Merge "Handle the power state change."
diff --git a/api/current.txt b/api/current.txt
index fcedbad..fb598fd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5324,7 +5324,6 @@
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
-    field public static final java.lang.String ACTION_SEND_PROVISIONING_VALUES = "android.app.action.ACTION_SEND_PROVISIONING_VALUES";
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
@@ -5341,7 +5340,6 @@
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.locale";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.localTime";
     field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.timeZone";
-    field public static final java.lang.String EXTRA_PROVISIONING_TOKEN = "android.app.extra.token";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.wifiHidden";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.wifiPacUrl";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PASSWORD = "android.app.extra.wifiPassword";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index df51ff5..fe51d82 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -86,20 +86,6 @@
     }
 
     /**
-     * Activity action: Used to indicate that the receiving activity is being started as part of the
-     * managed profile provisioning flow. This intent is typically sent to a mobile device
-     * management application (mdm) after the first part of the provisioning process is complete in
-     * the expectation that this app will (after optionally showing it's own UI) ultimately call
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE} to complete the creation of the managed profile.
-     *
-     * <p> The intent may contain the extras {@link #EXTRA_PROVISIONING_TOKEN} and
-     * {@link #EXTRA_PROVISIONING_EMAIL_ADDRESS}.
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_SEND_PROVISIONING_VALUES
-        = "android.app.action.ACTION_SEND_PROVISIONING_VALUES";
-
-    /**
      * Activity action: Starts the provisioning flow which sets up a managed profile.
      *
      * <p>A managed profile allows data separation for example for the usage of a
@@ -128,17 +114,6 @@
         = "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
 
     /**
-     * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the
-     * user has already consented to the creation of the managed profile.
-     * The intent must contain the extras
-     * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
-     * {@link #EXTRA_PROVISIONING_TOKEN}
-     * @hide
-     */
-    public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED
-        = "android.app.action.ACTION_PROVISIONING_USER_HAS_CONSENTED";
-
-    /**
      * A String extra holding the package name of the mobile device management application that
      * will be set as the profile owner or device owner.
      *
@@ -153,18 +128,6 @@
         = "android.app.extra.deviceAdminPackageName";
 
     /**
-     * An int extra used to identify that during the current setup process the user has already
-     * consented to setting up a managed profile. This is typically received by
-     * a mobile device management application when it is started with
-     * {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. The
-     * token indicates that steps asking for user consent can be skipped as the user has previously
-     * consented.
-     */
-    public static final String EXTRA_PROVISIONING_TOKEN
-        = "android.app.extra.token";
-
-    /**
      * A String extra holding the default name of the profile that is created during managed profile
      * provisioning.
      *
@@ -174,12 +137,15 @@
         = "android.app.extra.defaultManagedProfileName";
 
     /**
-     * A String extra holding the email address of the profile that is created during managed
-     * profile provisioning. This is typically received by a mobile management application when it
-     * is started with {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. It
-     * is eventually passed on in an intent
+     * A String extra that, holds the email address of the account which a managed profile is
+     * created for. Used with {@link #ACTION_PROVISION_MANAGED_PROFILE} and
      * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}.
+     *
+     * <p> If the {@link #ACTION_PROVISION_MANAGED_PROFILE} intent that starts managed provisioning
+     * contains this extra, it is forwarded in the
+     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} intent to the mobile
+     * device management application that was set as the profile owner during provisioning.
+     * It is usually used to avoid that the user has to enter their email address twice.
      */
     public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS
         = "android.app.extra.ManagedProfileEmailAddress";
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8b6f3d4..319559a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -723,6 +723,12 @@
         pkg.baseCodePath = apkPath;
         pkg.mSignatures = null;
 
+        // TODO: Remove this when the WebView can load resources dynamically. b/11505352
+        if (pkg.usesOptionalLibraries == null) {
+            pkg.usesOptionalLibraries = new ArrayList<String>();
+        }
+        pkg.usesOptionalLibraries.add("com.android.webview");
+
         return pkg;
     }
 
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index d6fa05a..755bb48 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -62,7 +62,7 @@
 import java.util.Locale;
 
 /**
- * A widget that enables the user to select a number form a predefined range.
+ * A widget that enables the user to select a number from a predefined range.
  * There are two flavors of this widget and which one is presented to the user
  * depends on the current theme.
  * <ul>
diff --git a/core/res/res/drawable-hdpi/notification_bg_low_normal.9.png b/core/res/res/drawable-hdpi/notification_bg_low_normal.9.png
deleted file mode 100644
index af91f5e..0000000
--- a/core/res/res/drawable-hdpi/notification_bg_low_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/notification_bg_low_pressed.9.png b/core/res/res/drawable-hdpi/notification_bg_low_pressed.9.png
deleted file mode 100644
index 9832ace..0000000
--- a/core/res/res/drawable-hdpi/notification_bg_low_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/notification_bg_normal.9.png b/core/res/res/drawable-hdpi/notification_bg_normal.9.png
deleted file mode 100644
index 6ebed8b..0000000
--- a/core/res/res/drawable-hdpi/notification_bg_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/notification_bg_normal_pressed.9.png b/core/res/res/drawable-hdpi/notification_bg_normal_pressed.9.png
deleted file mode 100644
index c271b11..0000000
--- a/core/res/res/drawable-hdpi/notification_bg_normal_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/notification_bg_low_normal.9.png b/core/res/res/drawable-mdpi/notification_bg_low_normal.9.png
deleted file mode 100644
index 62de9d7..0000000
--- a/core/res/res/drawable-mdpi/notification_bg_low_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/notification_bg_low_pressed.9.png b/core/res/res/drawable-mdpi/notification_bg_low_pressed.9.png
deleted file mode 100644
index 8a6011e..0000000
--- a/core/res/res/drawable-mdpi/notification_bg_low_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/notification_bg_normal.9.png b/core/res/res/drawable-mdpi/notification_bg_normal.9.png
deleted file mode 100644
index aa239b3..0000000
--- a/core/res/res/drawable-mdpi/notification_bg_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/notification_bg_normal_pressed.9.png b/core/res/res/drawable-mdpi/notification_bg_normal_pressed.9.png
deleted file mode 100644
index 525120d..0000000
--- a/core/res/res/drawable-mdpi/notification_bg_normal_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/notification_bg_low_normal.9.png b/core/res/res/drawable-xhdpi/notification_bg_low_normal.9.png
deleted file mode 100644
index 8c884de..0000000
--- a/core/res/res/drawable-xhdpi/notification_bg_low_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/notification_bg_low_pressed.9.png b/core/res/res/drawable-xhdpi/notification_bg_low_pressed.9.png
deleted file mode 100644
index 2159cf5..0000000
--- a/core/res/res/drawable-xhdpi/notification_bg_low_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/notification_bg_normal.9.png b/core/res/res/drawable-xhdpi/notification_bg_normal.9.png
deleted file mode 100644
index bdf477b..0000000
--- a/core/res/res/drawable-xhdpi/notification_bg_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/notification_bg_normal_pressed.9.png b/core/res/res/drawable-xhdpi/notification_bg_normal_pressed.9.png
deleted file mode 100644
index 3f054fb..0000000
--- a/core/res/res/drawable-xhdpi/notification_bg_normal_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/notification_bg_low_pressed.9.png b/core/res/res/drawable-xxhdpi/notification_bg_low_pressed.9.png
deleted file mode 100644
index b4e7559..0000000
--- a/core/res/res/drawable-xxhdpi/notification_bg_low_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/notification_bg_normal_pressed.9.png b/core/res/res/drawable-xxhdpi/notification_bg_normal_pressed.9.png
deleted file mode 100644
index 936fbe5..0000000
--- a/core/res/res/drawable-xxhdpi/notification_bg_normal_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/notification_bg.xml b/core/res/res/drawable/notification_bg.xml
deleted file mode 100644
index 362a524..0000000
--- a/core/res/res/drawable/notification_bg.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
-
-    <item android:state_pressed="true"  android:drawable="@drawable/notification_bg_normal_pressed" />
-    <item android:state_pressed="false" android:drawable="@drawable/notification_bg_normal" />
-</selector>
diff --git a/core/res/res/drawable/notification_bg_low.xml b/core/res/res/drawable/notification_bg_low.xml
deleted file mode 100644
index 466a885..0000000
--- a/core/res/res/drawable/notification_bg_low.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
-
-    <item android:state_pressed="true"  android:drawable="@drawable/notification_bg_low_pressed" />
-    <item android:state_pressed="false" android:drawable="@drawable/notification_bg_low_normal" />
-</selector>
diff --git a/core/res/res/drawable/notification_material_bg.xml b/core/res/res/drawable/notification_material_bg.xml
deleted file mode 100644
index 44c67be..0000000
--- a/core/res/res/drawable/notification_material_bg.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2014 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true">
-        <shape>
-            <solid android:color="#ffd0d0d0" />
-            <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
-        </shape>
-    </item>
-    <item>
-        <shape>
-            <solid android:color="#fffafafa" />
-            <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
-        </shape>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e94b9dd..d2e023d 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -365,7 +365,4 @@
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
     <dimen name="immersive_mode_cling_width">-1px</dimen>
 
-    <!-- radius of the corners of the material rounded rect background -->
-    <dimen name="notification_material_rounded_rect_radius">2dp</dimen>
-
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c68e6e6..5fea729 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -347,7 +347,6 @@
   <java-symbol type="dimen" name="notification_title_text_size" />
   <java-symbol type="dimen" name="notification_subtext_size" />
   <java-symbol type="dimen" name="immersive_mode_cling_width" />
-  <java-symbol type="dimen" name="notification_material_rounded_rect_radius" />
 
   <java-symbol type="string" name="add_account_button_label" />
   <java-symbol type="string" name="addToDictionary" />
@@ -1116,9 +1115,6 @@
   <java-symbol type="drawable" name="unlock_halo" />
   <java-symbol type="drawable" name="unlock_ring" />
   <java-symbol type="drawable" name="unlock_wave" />
-  <java-symbol type="drawable" name="notification_bg" />
-  <java-symbol type="drawable" name="notification_bg_dim" />
-  <java-symbol type="drawable" name="notification_bg_low" />
   <java-symbol type="drawable" name="notification_template_icon_bg" />
   <java-symbol type="drawable" name="notification_template_icon_low_bg" />
   <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
@@ -1695,8 +1691,6 @@
   <java-symbol type="color" name="notification_icon_bg_color" />
   <java-symbol type="drawable" name="notification_icon_legacy_bg" />
   <java-symbol type="drawable" name="notification_icon_legacy_bg_inset" />
-  <java-symbol type="drawable" name="notification_material_bg_dim" />
-  <java-symbol type="drawable" name="notification_material_bg" />
   <java-symbol type="drawable" name="notification_material_media_progress" />
   <java-symbol type="color" name="notification_media_action_bg" />
   <java-symbol type="color" name="notification_media_progress" />
diff --git a/media/java/android/media/AudioDevice.java b/media/java/android/media/AudioDevice.java
new file mode 100644
index 0000000..1fd27fe
--- /dev/null
+++ b/media/java/android/media/AudioDevice.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 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.media;
+
+import android.util.SparseIntArray;
+
+/**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ */
+public class AudioDevice {
+
+    public static final int DEVICE_TYPE_UNKNOWN          = 0;
+    public static final int DEVICE_TYPE_BUILTIN_EARPIECE = 1;
+    public static final int DEVICE_TYPE_BUILTIN_SPEAKER  = 2;
+    public static final int DEVICE_TYPE_WIRED_HEADSET    = 3;
+    public static final int DEVICE_TYPE_WIRED_HEADPHONES = 4;
+    public static final int DEVICE_TYPE_LINE_ANALOG      = 5;
+    public static final int DEVICE_TYPE_LINE_DIGITAL     = 6;
+    public static final int DEVICE_TYPE_BLUETOOTH_SCO    = 7;
+    public static final int DEVICE_TYPE_BLUETOOTH_A2DP   = 8;
+    public static final int DEVICE_TYPE_HDMI             = 9;
+    public static final int DEVICE_TYPE_HDMI_ARC         = 10;
+    public static final int DEVICE_TYPE_USB_DEVICE       = 11;
+    public static final int DEVICE_TYPE_USB_ACCESSORY    = 12;
+    public static final int DEVICE_TYPE_DOCK             = 13;
+    public static final int DEVICE_TYPE_FM               = 14;
+    public static final int DEVICE_TYPE_BUILTIN_MIC      = 15;
+    public static final int DEVICE_TYPE_FM_TUNER         = 16;
+    public static final int DEVICE_TYPE_TV_TUNER         = 17;
+    public static final int DEVICE_TYPE_TELEPHONY        = 18;
+
+    AudioDevicePortConfig mConfig;
+
+    AudioDevice(AudioDevicePortConfig config) {
+        mConfig = new AudioDevicePortConfig(config);
+    }
+
+    public boolean isInputDevice() {
+        return (mConfig.port().role() == AudioPort.ROLE_SOURCE);
+    }
+
+    public boolean isOutputDevice() {
+        return (mConfig.port().role() == AudioPort.ROLE_SINK);
+    }
+
+    public int getDeviceType() {
+        return INT_TO_EXT_DEVICE_MAPPING.get(mConfig.port().type(), DEVICE_TYPE_UNKNOWN);
+    }
+
+    public String getAddress() {
+        return mConfig.port().address();
+    }
+
+    private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING;
+
+    static {
+        INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray();
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, DEVICE_TYPE_BUILTIN_EARPIECE);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER, DEVICE_TYPE_BUILTIN_SPEAKER);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_WIRED_HEADSET, DEVICE_TYPE_WIRED_HEADSET);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, DEVICE_TYPE_WIRED_HEADPHONES);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, DEVICE_TYPE_BLUETOOTH_SCO);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, DEVICE_TYPE_BLUETOOTH_SCO);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT, DEVICE_TYPE_BLUETOOTH_SCO);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, DEVICE_TYPE_BLUETOOTH_A2DP);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, DEVICE_TYPE_BLUETOOTH_A2DP);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, DEVICE_TYPE_BLUETOOTH_A2DP);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HDMI, DEVICE_TYPE_HDMI);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, DEVICE_TYPE_DOCK);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, DEVICE_TYPE_DOCK);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_USB_ACCESSORY, DEVICE_TYPE_USB_ACCESSORY);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_USB_DEVICE, DEVICE_TYPE_USB_DEVICE);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_TELEPHONY_TX, DEVICE_TYPE_TELEPHONY);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_LINE, DEVICE_TYPE_LINE_ANALOG);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HDMI_ARC, DEVICE_TYPE_HDMI_ARC);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPDIF, DEVICE_TYPE_LINE_DIGITAL);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_FM, DEVICE_TYPE_FM);
+
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, DEVICE_TYPE_BUILTIN_MIC);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, DEVICE_TYPE_BLUETOOTH_SCO);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_WIRED_HEADSET, DEVICE_TYPE_WIRED_HEADSET);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_HDMI, DEVICE_TYPE_HDMI);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_TELEPHONY_RX, DEVICE_TYPE_TELEPHONY);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BACK_MIC, DEVICE_TYPE_BUILTIN_MIC);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET, DEVICE_TYPE_DOCK);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET, DEVICE_TYPE_DOCK);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_USB_ACCESSORY, DEVICE_TYPE_USB_ACCESSORY);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_USB_DEVICE, DEVICE_TYPE_USB_DEVICE);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_FM_TUNER, DEVICE_TYPE_FM_TUNER);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_TV_TUNER, DEVICE_TYPE_TV_TUNER);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_LINE, DEVICE_TYPE_LINE_ANALOG);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_SPDIF, DEVICE_TYPE_LINE_DIGITAL);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, DEVICE_TYPE_BLUETOOTH_A2DP);
+
+        // not covered here, legacy
+        //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
+        //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+    }
+}
+
diff --git a/media/java/android/media/AudioDevicePortConfig.java b/media/java/android/media/AudioDevicePortConfig.java
index a381e10..e468a53 100644
--- a/media/java/android/media/AudioDevicePortConfig.java
+++ b/media/java/android/media/AudioDevicePortConfig.java
@@ -31,6 +31,11 @@
         super((AudioPort)devicePort, samplingRate, channelMask, format, gain);
     }
 
+    AudioDevicePortConfig(AudioDevicePortConfig config) {
+        this(config.port(), config.samplingRate(), config.channelMask(), config.format(),
+                config.gain());
+    }
+
     /**
      * Returns the audio device port this AudioDevicePortConfig is issued from.
      */
diff --git a/core/res/res/drawable/notification_material_bg_dim.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
similarity index 74%
rename from core/res/res/drawable/notification_material_bg_dim.xml
rename to packages/SystemUI/res/drawable/notification_material_bg.xml
index 9b691e6..6a0277f 100644
--- a/core/res/res/drawable/notification_material_bg_dim.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -16,17 +16,11 @@
   -->
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="@color/white">
+        android:color="@color/notification_ripple_untinted_color">
     <item>
         <shape>
-            <solid android:color="#d4ffffff" />
+            <solid android:color="@color/notification_material_background_color" />
             <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
         </shape>
     </item>
-    <item android:id="@id/mask">
-        <shape>
-            <solid android:color="@color/white" />
-            <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
-        </shape>
-    </item>
-</ripple>
+</ripple>
\ No newline at end of file
diff --git a/core/res/res/drawable/notification_bg_dim.xml b/packages/SystemUI/res/drawable/notification_material_bg_dim.xml
similarity index 73%
rename from core/res/res/drawable/notification_bg_dim.xml
rename to packages/SystemUI/res/drawable/notification_material_bg_dim.xml
index 5c245f8..b04394d 100644
--- a/core/res/res/drawable/notification_bg_dim.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg_dim.xml
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#ff444444">
-    <item android:drawable="@drawable/notification_bg_normal" />
-</ripple>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/notification_material_background_dimmed_color" />
+    <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/notification_scrim.xml b/packages/SystemUI/res/drawable/notification_scrim.xml
index ff7e31f1..53ba213 100644
--- a/packages/SystemUI/res/drawable/notification_scrim.xml
+++ b/packages/SystemUI/res/drawable/notification_scrim.xml
@@ -17,6 +17,6 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#34000000" />
-    <corners android:radius="@*android:dimen/notification_material_rounded_rect_radius" />
+    <solid android:color="#08000000" />
+    <corners android:radius="@dimen/notification_material_rounded_rect_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_panel_background.xml b/packages/SystemUI/res/drawable/qs_panel_background.xml
index e579d87..b4e311f 100644
--- a/packages/SystemUI/res/drawable/qs_panel_background.xml
+++ b/packages/SystemUI/res/drawable/qs_panel_background.xml
@@ -18,6 +18,6 @@
     <corners
         android:topLeftRadius="0dp"
         android:topRightRadius="0dp"
-        android:bottomLeftRadius="@*android:dimen/notification_material_rounded_rect_radius"
-        android:bottomRightRadius="@*android:dimen/notification_material_rounded_rect_radius"/>
+        android:bottomLeftRadius="@dimen/notification_material_rounded_rect_radius"
+        android:bottomRightRadius="@dimen/notification_material_rounded_rect_radius"/>
 </shape>
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index e1c460c..ca46437 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -17,41 +17,36 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/system_primary_color" >
-
-    <ImageView
-        android:id="@android:id/button1"
-        android:layout_width="64dp"
-        android:layout_height="64dp"
-        android:layout_alignParentStart="true"
-        android:contentDescription="@string/accessibility_quick_settings_close"
-        android:padding="@dimen/qs_panel_padding"
-        android:src="@drawable/ic_qs_back" />
+    android:background="@color/system_primary_color"
+    android:padding="16dp" >
 
     <TextView
-        android:id="@android:id/title"
-        android:layout_width="match_parent"
-        android:layout_height="64dp"
-        android:layout_alignParentTop="true"
-        android:layout_toEndOf="@android:id/button1"
-        android:layout_toStartOf="@android:id/checkbox"
-        android:gravity="center_vertical"
-        android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
-
-    <ImageView
-        android:id="@android:id/custom"
-        android:layout_width="match_parent"
+        android:id="@android:id/button1"
+        style="@style/QSBorderlessButton"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_below="@android:id/title"
-        android:layout_marginLeft="16dip"
-        android:layout_marginRight="16dip"
-        android:scaleType="fitXY"
-        android:src="?android:attr/dividerHorizontal" />
+        android:minWidth="88dp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:text="@string/quick_settings_done"
+        android:textAppearance="@style/TextAppearance.QS.DetailButton" />
+
+    <TextView
+        android:id="@android:id/button2"
+        style="@style/QSBorderlessButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginEnd="8dp"
+        android:minWidth="132dp"
+        android:layout_toStartOf="@android:id/button1"
+        android:text="@string/quick_settings_more_settings"
+        android:textAppearance="@style/TextAppearance.QS.DetailButton" />
 
     <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_below="@android:id/custom" />
+        android:layout_above="@android:id/button1" />
 
 </RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
new file mode 100644
index 0000000..fcbb32c
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_header.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/BrightnessDialogContainer"
+    android:background="@drawable/btn_borderless_rect" >
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
+
+    <Switch
+        android:id="@android:id/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clickable="false"
+        android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
new file mode 100644
index 0000000..c5eaed9
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/qs_detail_item_height"
+    android:gravity="center_vertical"
+    android:background="@drawable/btn_borderless_rect"
+    android:clickable="true"
+    android:orientation="horizontal" >
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginEnd="12dp" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="20dp"
+        android:orientation="vertical" >
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="2dp"
+            android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index 97ed9a0..58547b9 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -30,7 +30,7 @@
     <com.android.systemui.settings.ToggleSlider
         android:id="@+id/brightness_slider"
         android:layout_width="0dp"
-        android:layout_height="44dp"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
         android:layout_weight="1"
         systemui:text="@string/status_bar_settings_auto_brightness_label" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 2188a8e..1f68cd8 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -56,12 +56,6 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
-        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
-            android:id="@+id/notification_stack_scroller"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_marginBottom="@dimen/close_handle_underlap"/>
-
         <ViewStub
                 android:id="@+id/keyguard_user_switcher"
                 android:layout_height="wrap_content"
@@ -90,12 +84,25 @@
                     android:layout_marginRight="@dimen/notification_side_padding"/>
 
                 <!-- A view to reserve space for the collapsed stack -->
+                <!-- Layout height: notification_min_height + bottom_stack_peek_amount -->
                 <View
-                    android:layout_height="@dimen/collapsed_stack_height"
-                    android:layout_width="match_parent"/>
+                    android:id="@+id/reserve_notification_space"
+                    android:layout_height="@dimen/min_stack_height"
+                    android:layout_width="match_parent"
+                    android:layout_marginTop="@dimen/notifications_top_padding" />
+
+                <View
+                    android:layout_height="@dimen/notification_side_padding"
+                    android:layout_width="match_parent" />
             </LinearLayout>
         </com.android.systemui.statusbar.phone.ObservableScrollView>
 
+        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
+            android:id="@+id/notification_stack_scroller"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginBottom="@dimen/close_handle_underlap"/>
+
     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
 
     <include layout="@layout/status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index d239208..70589b7 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -135,13 +135,22 @@
         android:textColor="#ffffff"
         android:singleLine="true" />
 
-    <include
-        layout="@layout/quick_settings_brightness_dialog"
-        android:id="@+id/brightness_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        />
+
+        <include
+            android:id="@+id/qs_detail_header"
+            layout="@layout/qs_detail_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            />
+        <include
+            android:id="@+id/brightness_container"
+            layout="@layout/quick_settings_brightness_dialog"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            />
+
 
     <TextView
         android:id="@+id/header_debug_info"
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index 6d63bb0..6ab8cf3 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -41,7 +41,7 @@
 
     <ImageView
         android:id="@android:id/button1"
-        style="@style/BorderlessButton"
+        style="@style/QSBorderlessButton"
         android:layout_width="@dimen/zen_mode_condition_height"
         android:layout_height="@dimen/zen_mode_condition_height"
         android:layout_alignParentEnd="true"
@@ -53,7 +53,7 @@
 
     <ImageView
         android:id="@android:id/button2"
-        style="@style/BorderlessButton"
+        style="@style/QSBorderlessButton"
         android:layout_width="@dimen/zen_mode_condition_height"
         android:layout_height="@dimen/zen_mode_condition_height"
         android:layout_alignParentEnd="true"
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 0a8f852..0420cbc 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -42,7 +42,7 @@
 
     <TextView
         android:id="@android:id/button2"
-        style="@style/BorderlessButton"
+        style="@style/QSBorderlessButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentEnd="true"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 19d72c4..a93768b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -60,6 +60,24 @@
 
     <color name="keyguard_affordance">#ffffffff</color>
 
+    <!-- The color of the legacy notification background -->
+    <color name="notification_legacy_background_color">#ff1a1a1a</color>
+
+    <!-- The color of the material notification background -->
+    <color name="notification_material_background_color">#fffafafa</color>
+
+    <!-- The color of the material notification background when dimmed -->
+    <color name="notification_material_background_dimmed_color">#d4ffffff</color>
+
+    <!-- The color of the material notification background when low priority -->
+    <color name="notification_material_background_low_priority_color">#ffdcdcdc</color>
+
+    <!-- The color of the ripples on the untinted notifications -->
+    <color name="notification_ripple_untinted_color">#20000000</color>
+
+    <!-- The color of the ripples on the tinted notifications -->
+    <color name="notification_ripple_tinted_color">#30ffffff</color>
+
     <!-- The color of the circle around the primary user in the user switcher -->
     <color name="current_user_border_color">@color/primary_color</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8ee9155..adec883 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -168,6 +168,9 @@
     <dimen name="qs_tile_padding_bottom">16dp</dimen>
     <dimen name="qs_tile_spacing">4dp</dimen>
     <dimen name="qs_panel_padding_bottom">8dp</dimen>
+    <dimen name="qs_detail_item_height">48dp</dimen>
+    <dimen name="qs_detail_item_height_connected">72dp</dimen>
+
 
     <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
     <dimen name="qs_peek_height">8dp</dimen>
@@ -227,6 +230,9 @@
     <!-- Space reserved for the cards behind the top card in the bottom stack -->
     <dimen name="bottom_stack_peek_amount">12dp</dimen>
 
+    <!-- bottom_stack_peek_amount + notification_min_height -->
+    <dimen name="min_stack_height">76dp</dimen>
+
     <!-- The height of the area before the bottom stack in which the notifications slow down -->
     <dimen name="bottom_stack_slow_down_length">12dp</dimen>
 
@@ -248,9 +254,6 @@
     <!-- The height of the speed bump view. -->
     <dimen name="speed_bump_height">16dp</dimen>
 
-    <!-- The total height of the stack in its collapsed size (i.e. when quick settings is open) -->
-    <dimen name="collapsed_stack_height">94dp</dimen>
-
     <!-- Width of the zen mode interstitial dialog. -->
     <dimen name="zen_mode_dialog_width">320dp</dimen>
 
@@ -306,6 +309,9 @@
          phone hints. -->
     <dimen name="edge_tap_area_width">48dp</dimen>
 
+    <!-- radius of the corners of the material rounded rect background -->
+    <dimen name="notification_material_rounded_rect_radius">2dp</dimen>
+
     <!-- end margin for multi user switch in expanded quick settings -->
     <dimen name="multi_user_switch_expanded_margin">8dp</dimen>
 
diff --git a/packages/SystemUI/res/values/internal.xml b/packages/SystemUI/res/values/internal.xml
index 7b93d31..3b593d2 100644
--- a/packages/SystemUI/res/values/internal.xml
+++ b/packages/SystemUI/res/values/internal.xml
@@ -17,6 +17,5 @@
 <resources>
     <dimen name="status_bar_height">@*android:dimen/status_bar_height</dimen>
     <dimen name="navigation_bar_height">@*android:dimen/navigation_bar_height</dimen>
-    <drawable name="notification_material_bg">@*android:drawable/notification_material_bg</drawable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a8799f7..a5cfdbc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -534,6 +534,10 @@
     <string name="quick_settings_color_space_label">Color correction mode</string>
     <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_more_settings">More settings</string>
+    <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_done">Done</string>
+    <!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_connected">Connected</string>
     <!-- QuickSettings: Tethering. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_tethering_label">Tethering</string>
     <!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e5d5b03..dd1b749 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -176,6 +176,7 @@
         <item name="android:textSize">14sp</item>
         <item name="android:textAllCaps">true</item>
         <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:gravity">center</item>
     </style>
 
     <style name="BaseBrightnessDialogContainer">
@@ -233,6 +234,11 @@
         <item name="android:colorControlActivated">#ffffffff</item>
     </style>
 
+     <style name="QSBorderlessButton">
+        <item name="android:padding">12dp</item>
+        <item name="android:background">@drawable/btn_borderless_rect</item>
+        <item name="android:gravity">center</item>
+    </style>
     <style name="BorderlessButton" parent="@android:style/Widget.Material.Button.Borderless" />
 
     <style name="BorderlessButton.Tiny">
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CircularClipper.java b/packages/SystemUI/src/com/android/systemui/qs/CircularClipper.java
index ff904b3..327ed6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CircularClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CircularClipper.java
@@ -21,23 +21,19 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.view.View;
+import android.view.ViewAnimationUtils;
 
 /** Helper for view-level circular clip animations. **/
 public class CircularClipper {
 
     private final View mTarget;
 
-    private Utils mUtils;
     private ValueAnimator mAnimator;
 
     public CircularClipper(View target) {
         mTarget = target;
     }
 
-    public void setUtils(Utils utils) {
-        mUtils = utils;
-    }
-
     public void animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
         if (mAnimator != null) {
             mAnimator.cancel();
@@ -49,14 +45,7 @@
         r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + h * h)));
         r = (int) Math.max(r, Math.ceil(Math.sqrt(x * x + h * h)));
 
-        if (mUtils == null) {
-                mTarget.setVisibility(in ? View.VISIBLE : View.GONE);
-            if (listener != null) {
-                listener.onAnimationEnd(null);
-            }
-            return;
-        }
-        mAnimator = mUtils.createRevealAnimator(mTarget, x, y, 0, r);
+        mAnimator = ViewAnimationUtils.createCircularReveal(mTarget, x, y, 0, r);
         mAnimator.removeAllListeners();
         if (listener != null) {
             mAnimator.addListener(listener);
@@ -83,9 +72,4 @@
             mTarget.setVisibility(View.GONE);
         };
     };
-
-    public interface Utils {
-        ValueAnimator createRevealAnimator(View v, int centerX,  int centerY,
-                float startRadius, float endRadius);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index d152887..51befd6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -20,15 +20,17 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.FrameLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.QSTile.DetailAdapter;
 
 import java.util.ArrayList;
 
@@ -38,7 +40,10 @@
 
     private final Context mContext;
     private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
-    private final FrameLayout mDetail;
+    private final View mDetail;
+    private final ViewGroup mDetailContent;
+    private final View mDetailSettingsButton;
+    private final View mDetailDoneButton;
     private final CircularClipper mClipper;
     private final H mHandler = new H();
 
@@ -63,8 +68,10 @@
         super(context, attrs);
         mContext = context;
 
-        mDetail = new FrameLayout(mContext);
-        mDetail.setBackgroundColor(mContext.getResources().getColor(R.color.system_primary_color));
+        mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false);
+        mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content);
+        mDetailSettingsButton = mDetail.findViewById(android.R.id.button2);
+        mDetailDoneButton = mDetail.findViewById(android.R.id.button1);
         mDetail.setVisibility(GONE);
         mDetail.setClickable(true);
         addView(mDetail);
@@ -91,10 +98,6 @@
         }
     }
 
-    public void setUtils(CircularClipper.Utils utils) {
-        mClipper.setUtils(utils);
-    }
-
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
@@ -141,6 +144,12 @@
             public void onShowDetail(boolean show) {
                 QSPanel.this.showDetail(show, r);
             }
+            @Override
+            public void onToggleStateChanged(boolean state) {
+                if (mDetailRecord == r) {
+                    fireToggleStateChanged(state);
+                }
+            }
         });
         final View.OnClickListener click = new View.OnClickListener() {
             @Override
@@ -165,20 +174,34 @@
         if (r == null) return;
         AnimatorListener listener = null;
         if (show) {
-            if (mDetailRecord != null) return;
-            if (r.detailView == null) {
-                r.detailView = r.tile.createDetailView(mContext, mDetail);
-            }
-            if (r.detailView == null) return;
+            if (mDetailRecord != null) return;  // already showing something in detail
+            r.detailAdapter = r.tile.getDetailAdapter();
+            if (r.detailAdapter == null) return;
+            r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
+            if (r.detailView == null) throw new IllegalStateException("Must return detail view");
+            mDetailDoneButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    showDetail(false, mDetailRecord);
+                }
+            });
+            final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
+            mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
+            mDetailSettingsButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
+                }
+            });
             mDetailRecord = r;
-            mDetail.removeAllViews();
+            mDetailContent.removeAllViews();
             mDetail.bringToFront();
-            mDetail.addView(r.detailView);
+            mDetailContent.addView(r.detailView);
         } else {
             if (mDetailRecord == null) return;
             listener = mTeardownDetailWhenDone;
         }
-        fireShowingDetail(show);
+        fireShowingDetail(show ? r.detailAdapter : null);
         int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
         int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
         mClipper.animateCircularClip(x, y, show, listener);
@@ -215,11 +238,7 @@
             record.tileView.measure(exactly(cw), exactly(ch));
         }
         int h = rows == 0 ? 0 : (getRowTop(rows) + mPanelPaddingBottom);
-        mDetail.measure(exactly(width), unspecified());
-        if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) {
-            final int dmh = mDetail.getMeasuredHeight();
-            if (dmh > 0) h = Math.max(h, dmh);
-        }
+        mDetail.measure(exactly(width), exactly(h));
         setMeasuredDimension(width, h);
     }
 
@@ -227,10 +246,6 @@
         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
     }
 
-    private static int unspecified() {
-        return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-    }
-
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final int w = getWidth();
@@ -263,9 +278,15 @@
         return cols;
     }
 
-    private void fireShowingDetail(boolean showingDetail) {
+    private void fireShowingDetail(QSTile.DetailAdapter detail) {
         if (mCallback != null) {
-            mCallback.onShowingDetail(showingDetail);
+            mCallback.onShowingDetail(detail);
+        }
+    }
+
+    private void fireToggleStateChanged(boolean state) {
+        if (mCallback != null) {
+            mCallback.onToggleStateChanged(state);
         }
     }
 
@@ -286,18 +307,20 @@
         QSTile<?> tile;
         QSTileView tileView;
         View detailView;
+        DetailAdapter detailAdapter;
         int row;
         int col;
     }
 
     private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
         public void onAnimationEnd(Animator animation) {
-            mDetail.removeAllViews();
+            mDetailContent.removeAllViews();
             mDetailRecord = null;
         };
     };
 
     public interface Callback {
-        void onShowingDetail(boolean showingDetail);
+        void onShowingDetail(QSTile.DetailAdapter detail);
+        void onToggleStateChanged(boolean state);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index ba350e5..62c9d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -26,7 +26,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.systemui.R;
 import com.android.systemui.qs.QSTile.State;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -57,7 +56,6 @@
     protected final Context mContext;
     protected final H mHandler;
     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
-    private final int mFeedbackStartDelay;
 
     private Callback mCallback;
     protected final TState mState = newTileState();
@@ -71,7 +69,6 @@
         mHost = host;
         mContext = host.getContext();
         mHandler = new H(host.getLooper());
-        mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
     }
 
     public boolean supportsDualTargets() {
@@ -86,10 +83,18 @@
         return new QSTileView(context);
     }
 
-    public View createDetailView(Context context, ViewGroup root) {
+    public DetailAdapter getDetailAdapter() {
         return null; // optional
     }
 
+    public interface DetailAdapter {
+        int getTitle();
+        Boolean getToggleState();
+        View createDetailView(Context context, View convertView, ViewGroup parent);
+        Intent getSettingsIntent();
+        void setToggleState(boolean state);
+    }
+
     // safe to call from any thread
 
     public void setCallback(Callback callback) {
@@ -120,8 +125,8 @@
         mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget();
     }
 
-    protected void postAfterFeedback(Runnable runnable) {
-        mHandler.postDelayed(runnable, mFeedbackStartDelay);
+    public void fireToggleStateChanged(boolean state) {
+        mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
     }
 
     // call only on tile worker looper
@@ -155,6 +160,12 @@
         }
     }
 
+    private void handleToggleStateChanged(boolean state) {
+        if (mCallback != null) {
+            mCallback.onToggleStateChanged(state);
+        }
+    }
+
     protected void handleUserSwitch(int newUserId) {
         handleRefreshState(null);
     }
@@ -166,6 +177,7 @@
         private static final int REFRESH_STATE = 4;
         private static final int SHOW_DETAIL = 5;
         private static final int USER_SWITCH = 6;
+        private static final int TOGGLE_STATE_CHANGED = 7;
 
         private H(Looper looper) {
             super(looper);
@@ -193,6 +205,9 @@
                 } else if (msg.what == USER_SWITCH) {
                     name = "handleUserSwitch";
                     handleUserSwitch(msg.arg1);
+                } else if (msg.what == TOGGLE_STATE_CHANGED) {
+                    name = "handleToggleStateChanged";
+                    handleToggleStateChanged(msg.arg1 != 0);
                 }
             } catch (Throwable t) {
                 final String error = "Error in " + name;
@@ -205,6 +220,7 @@
     public interface Callback {
         void onStateChanged(State state);
         void onShowDetail(boolean show);
+        void onToggleStateChanged(boolean state);
     }
 
     public interface Host {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java
index 07ea825..a308e84 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java
@@ -58,7 +58,7 @@
 
     @Override
     protected void handleClick() {
-        postAfterFeedback(new Runnable() {
+        mHandler.post(new Runnable() {
             @Override
             public void run() {
                 mHost.collapsePanels();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 6793051..502713f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -65,7 +65,7 @@
 
     @Override
     protected void handleClick() {
-        postAfterFeedback(new Runnable() {
+        mHandler.post(new Runnable() {
             public void run() {
                 mHost.collapsePanels();
                 mUiHandler.post(mShowDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
index c4bdb19..3bdea79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
@@ -22,11 +22,9 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
@@ -47,45 +45,8 @@
     }
 
     @Override
-    public View createDetailView(Context context, ViewGroup root) {
-        final View v = LayoutInflater.from(context).inflate(R.layout.qs_detail, root, false);
-        final TextView title = (TextView) v.findViewById(android.R.id.title);
-        title.setText(R.string.quick_settings_notifications_label);
-        final View close = v.findViewById(android.R.id.button1);
-        close.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                showDetail(false);
-            }
-        });
-        final ViewGroup content = (ViewGroup) v.findViewById(android.R.id.content);
-        final VolumeComponent volumeComponent = mHost.getVolumeComponent();
-        final VolumePanel vp = new VolumePanel(mContext, content, mZenController);
-        v.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
-            @Override
-            public void onViewDetachedFromWindow(View v) {
-                volumeComponent.setVolumePanel(null);
-            }
-
-            @Override
-            public void onViewAttachedToWindow(View v) {
-                vp.updateStates();
-                volumeComponent.setVolumePanel(vp);
-            }
-        });
-        vp.setZenModePanelCallback(new ZenModePanel.Callback() {
-            @Override
-            public void onMoreSettings() {
-                mHost.startSettingsActivity(ZenModePanel.ZEN_SETTINGS);
-            }
-
-            @Override
-            public void onInteraction() {
-                // noop
-            }
-        });
-        vp.postVolumeChanged(AudioManager.STREAM_RING, AudioManager.FLAG_SHOW_UI);
-        return v;
+    public DetailAdapter getDetailAdapter() {
+        return mDetailAdapter;
     }
 
     @Override
@@ -164,4 +125,58 @@
             }
         }
     };
+
+    private final DetailAdapter mDetailAdapter = new DetailAdapter() {
+
+        @Override
+        public int getTitle() {
+            return R.string.quick_settings_notifications_label;
+        }
+
+        @Override
+        public Boolean getToggleState() {
+            return null;
+        }
+
+        public void setToggleState(boolean state) {
+            // noop
+        }
+
+        public Intent getSettingsIntent() {
+            return ZenModePanel.ZEN_SETTINGS;
+        }
+
+        @Override
+        public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            if (convertView != null) return convertView;
+            final VolumeComponent volumeComponent = mHost.getVolumeComponent();
+            final VolumePanel vp = new VolumePanel(mContext, parent, mZenController);
+            final View v = vp.getContentView();
+            v.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    volumeComponent.setVolumePanel(null);
+                }
+
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    vp.updateStates();
+                    volumeComponent.setVolumePanel(vp);
+                }
+            });
+            vp.setZenModePanelCallback(new ZenModePanel.Callback() {
+                @Override
+                public void onMoreSettings() {
+                    mHost.startSettingsActivity(ZenModePanel.ZEN_SETTINGS);
+                }
+
+                @Override
+                public void onInteraction() {
+                    // noop
+                }
+            });
+            vp.postVolumeChanged(AudioManager.STREAM_RING, AudioManager.FLAG_SHOW_UI);
+            return v;
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index a236497..84eee24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -21,23 +21,34 @@
 import android.content.res.Resources;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPoint;
 import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
 
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTile<QSTile.SignalState> {
     private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
+    private static final int MAX_ITEMS = 4; // TODO temporary visual restriction
 
     private final NetworkController mController;
+    private final WifiDetailAdapter mDetailAdapter;
 
     public WifiTile(Host host) {
         super(host);
         mController = host.getNetworkController();
+        mDetailAdapter = new WifiDetailAdapter();
     }
 
     @Override
@@ -54,12 +65,20 @@
     public void setListening(boolean listening) {
         if (listening) {
             mController.addNetworkSignalChangedCallback(mCallback);
+            mController.addAccessPointCallback(mDetailAdapter);
+            mController.scanForAccessPoints();
         } else {
             mController.removeNetworkSignalChangedCallback(mCallback);
+            mController.removeAccessPointCallback(mDetailAdapter);
         }
     }
 
     @Override
+    public DetailAdapter getDetailAdapter() {
+        return mDetailAdapter;
+    }
+
+    @Override
     public QSTileView createTileView(Context context) {
         return new SignalTileView(context);
     }
@@ -71,7 +90,7 @@
 
     @Override
     protected void handleSecondaryClick() {
-        mHost.startSettingsActivity(WIFI_SETTINGS);
+        showDetail(true);
     }
 
     @Override
@@ -83,6 +102,11 @@
 
         boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.enabledDesc != null);
         boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null);
+        boolean enabledChanging = state.enabled != cb.enabled;
+        if (enabledChanging) {
+            mDetailAdapter.postUpdateItems();
+            fireToggleStateChanged(cb.enabled);
+        }
         state.enabled = cb.enabled;
         state.connected = wifiConnected;
         state.activityIn = cb.enabled && cb.activityIn;
@@ -169,4 +193,96 @@
             // noop
         }
     };
+
+    private final class WifiDetailAdapter implements DetailAdapter,
+            NetworkController.AccessPointCallback {
+
+        private LinearLayout mItems;
+        private AccessPoint[] mAccessPoints;
+
+        @Override
+        public int getTitle() {
+            return R.string.quick_settings_wifi_label;
+        }
+
+        public Intent getSettingsIntent() {
+            return WIFI_SETTINGS;
+        }
+
+        @Override
+        public Boolean getToggleState() {
+            return mState.enabled;
+        }
+
+        @Override
+        public void setToggleState(boolean state) {
+            if (DEBUG) Log.d(TAG, "setToggleState " + state);
+            mController.setWifiEnabled(state);
+            showDetail(false);
+        }
+
+        @Override
+        public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            if (convertView != null) return convertView;
+            mItems = new LinearLayout(context);
+            mItems.setOrientation(LinearLayout.VERTICAL);
+            updateItems();
+            return mItems;
+        }
+
+        @Override
+        public void onAccessPointsChanged(final AccessPoint[] accessPoints) {
+            mUiHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mAccessPoints = accessPoints;
+                    updateItems();
+                }
+            });
+        }
+
+        public void postUpdateItems() {
+            mUiHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    updateItems();
+                }
+            });
+        }
+
+        private void updateItems() {
+            if (mItems == null) return;
+            mItems.removeAllViews();
+            if (mAccessPoints == null || mAccessPoints.length == 0 || !mState.enabled) return;
+            for (int i = 0; i < mAccessPoints.length; i++) {
+                final AccessPoint ap = mAccessPoints[i];
+                if (ap == null) continue;
+                final View item = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item,
+                        mItems, false);
+                final ImageView iv = (ImageView) item.findViewById(android.R.id.icon);
+                iv.setImageResource(ap.iconId);
+                final TextView title = (TextView) item.findViewById(android.R.id.title);
+                title.setText(ap.ssid);
+                final TextView summary = (TextView) item.findViewById(android.R.id.summary);
+                if (ap.isConnected) {
+                    item.setMinimumHeight(mContext.getResources()
+                            .getDimensionPixelSize(R.dimen.qs_detail_item_height_connected));
+                    summary.setText(R.string.quick_settings_connected);
+                } else {
+                    summary.setVisibility(View.GONE);
+                }
+                item.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        if (!ap.isConnected) {
+                            mController.connect(ap);
+                        }
+                        showDetail(false);
+                    }
+                });
+                mItems.addView(item);
+                if (mItems.getChildCount() == MAX_ITEMS) break;
+            }
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Console.java b/packages/SystemUI/src/com/android/systemui/recents/Console.java
index 33e05dd..0cb74b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Console.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Console.java
@@ -43,7 +43,7 @@
     public static final String AnsiWhite = "\u001B[37m";
 
     // Console enabled state
-    public static final boolean Enabled = false;
+    public static boolean Enabled = false;
 
     /** Logs a key */
     public static void log(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 4fb1918..7a49a04 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -55,9 +55,9 @@
             public static final String TimeRecentsStartupKey = "startup";
             public static final String TimeRecentsLaunchKey = "launchTask";
             public static final String TimeRecentsScreenshotTransitionKey = "screenshot";
-            public static final boolean TimeRecentsStartup = true;
-            public static final boolean TimeRecentsLaunchTask = true;
-            public static final boolean TimeRecentsScreenshotTransition = true;
+            public static final boolean TimeRecentsStartup = false;
+            public static final boolean TimeRecentsLaunchTask = false;
+            public static final boolean TimeRecentsScreenshotTransition = false;
 
 
             public static final boolean RecentsComponent = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4c52b24..19a38c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -65,7 +65,6 @@
     AppWidgetHostView mSearchAppWidgetHostView;
 
     boolean mVisible;
-    boolean mTaskLaunched;
 
     // Runnables to finish the Recents activity
     FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
@@ -198,18 +197,24 @@
                 AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
         mConfig.launchedWithNoRecentTasks = !root.hasTasks();
 
-        // Show the scrim if we animate into Recents without window transitions
-        mScrimViews.prepareEnterRecentsAnimation();
-
         // Add the default no-recents layout
         if (mEmptyView == null) {
             mEmptyView = mEmptyViewStub.inflate();
         }
         if (mConfig.launchedWithNoRecentTasks) {
             mEmptyView.setVisibility(View.VISIBLE);
+            mRecentsView.setSearchBarVisibility(View.GONE);
         } else {
             mEmptyView.setVisibility(View.GONE);
+            if (mRecentsView.hasSearchBar()) {
+                mRecentsView.setSearchBarVisibility(View.VISIBLE);
+            } else {
+                addSearchBarAppWidgetView();
+            }
         }
+
+        // Show the scrim if we animate into Recents without window transitions
+        mScrimViews.prepareEnterRecentsAnimation();
     }
 
     /** Attempts to allocate and bind the search bar app widget */
@@ -352,20 +357,10 @@
         mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
         mScrimViews = new SystemBarScrimViews(this, mConfig);
 
-        // Update the recent tasks
-        updateRecentsTasks(getIntent());
-
-        // Prepare the screenshot transition if necessary
-        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
-            mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
-            mFullScreenOverlayView.setCallbacks(this);
-            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
-        }
-
         // Bind the search app widget when we first start up
         bindSearchBarAppWidget();
-        // Add the search bar layout
-        addSearchBarAppWidgetView();
+        // Update the recent tasks
+        updateRecentsTasks(getIntent());
 
         // Update if we are getting a configuration change
         if (savedInstanceState != null) {
@@ -390,6 +385,13 @@
         } catch (InvocationTargetException e) {
             e.printStackTrace();
         }
+
+        // Prepare the screenshot transition if necessary
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
+            mFullScreenOverlayView.setCallbacks(this);
+            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
     }
 
     void onConfigurationChange() {
@@ -404,8 +406,6 @@
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
-        // Reset the task launched flag if we encounter an onNewIntent() before onStop()
-        mTaskLaunched = false;
 
         if (Console.Enabled) {
             Console.logDivider(Constants.Log.App.SystemUIHandshake);
@@ -426,9 +426,6 @@
         if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
             mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
         }
-
-        // Don't attempt to rebind the search bar widget, but just add the search bar layout
-        addSearchBarAppWidgetView();
     }
 
     @Override
@@ -509,7 +506,6 @@
         }
 
         mVisible = false;
-        mTaskLaunched = false;
     }
 
     @Override
@@ -632,15 +628,13 @@
     }
 
     @Override
-    public void onTaskLaunching() {
-        mTaskLaunched = true;
-
+    public void onTaskViewClicked() {
         // Mark recents as no longer visible
         AlternateRecentsComponent.notifyVisibilityChanged(false);
     }
 
     @Override
-    public void onLastTaskRemoved() {
+    public void onAllTaskViewsDismissed() {
         mFinishLaunchHomeRunnable.run();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index d57f779..3041a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -149,6 +149,9 @@
 
         // Debug mode
         debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+        if (debugModeEnabled) {
+            Console.Enabled = true;
+        }
 
         // Animations
         animationPxMovementPerSecond =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index b6895d1..49149a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -69,7 +69,7 @@
                 // since that is done when we compute the animation itself in the Recents component
 
                 // Create a dummy task stack & compute the rect for the thumbnail to animate to
-                TaskStack stack = new TaskStack(context);
+                TaskStack stack = new TaskStack();
                 TaskStackView tsv = new TaskStackView(context, stack);
                 TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
                 Bundle replyData = new Bundle();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 15f4a76..9d8d746 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -239,13 +239,14 @@
                                 }
                                 thumbnail.setHasAlpha(false);
                                 loadThumbnail = thumbnail;
-                                mThumbnailCache.put(t.key, thumbnail);
                             } else {
                                 loadThumbnail = mDefaultThumbnail;
                                 Console.logError(mContext,
                                         "Failed to load task top thumbnail for: " +
                                                 t.key.baseIntent.getComponent().getPackageName());
                             }
+                            // We put the default thumbnail in the cache anyways
+                            mThumbnailCache.put(t.key, loadThumbnail);
                         }
                     }
                     if (!mCancelled) {
@@ -321,6 +322,17 @@
                     " iconCache: " + iconCacheSize);
         }
 
+        // Create the default assets
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        icon.eraseColor(0x00000000);
+        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        mDefaultThumbnail.setHasAlpha(false);
+        mDefaultThumbnail.eraseColor(0xFFffffff);
+        mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        mLoadingThumbnail.setHasAlpha(false);
+        mLoadingThumbnail.eraseColor(0xFFffffff);
+        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
+
         // Initialize the proxy, cache and loaders
         mSystemServicesProxy = new SystemServicesProxy(context);
         mPackageMonitor = new RecentsPackageMonitor();
@@ -330,14 +342,6 @@
         mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
                 mDefaultThumbnail);
 
-        // Create the default assets
-        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        icon.eraseColor(0x00000000);
-        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        mDefaultThumbnail.eraseColor(0xFFffffff);
-        mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        mLoadingThumbnail.eraseColor(0x00000000);
-        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
         if (Console.Enabled) {
             Console.log(Constants.Log.App.TaskDataLoader,
                     "[RecentsTaskLoader|defaultBitmaps]",
@@ -392,7 +396,7 @@
         RecentsConfiguration config = RecentsConfiguration.getInstance();
         Resources res = context.getResources();
         ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
-        TaskStack stack = new TaskStack(context);
+        TaskStack stack = new TaskStack();
         SpaceNode root = new SpaceNode(context);
         root.setStack(stack);
 
@@ -416,7 +420,9 @@
                 activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
                 activityIcon = (av.getIcon() != null) ?
                         ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null;
-                activityColor = av.getPrimaryColor();
+                if (av.getPrimaryColor() != 0) {
+                    activityColor = av.getPrimaryColor();
+                }
             } else {
                 activityLabel = ssp.getActivityLabel(info);
             }
@@ -464,10 +470,10 @@
                         task.thumbnail = ssp.getTaskThumbnail(task.key.id);
                         if (task.thumbnail != null) {
                             task.thumbnail.setHasAlpha(false);
-                            mThumbnailCache.put(task.key, task.thumbnail);
                         } else {
                             task.thumbnail = mDefaultThumbnail;
                         }
+                        mThumbnailCache.put(task.key, task.thumbnail);
                     } else {
                         // Either the task has updated, or we haven't cached any information for the
                         // task, so reload it
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index d2de185..24e01bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -139,13 +139,10 @@
         public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
     }
 
-    Context mContext;
-
     FilteredTaskList mTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
 
-    public TaskStack(Context context) {
-        mContext = context;
+    public TaskStack() {
     }
 
     /** Sets the callbacks for this task stack */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index b5b9cb5..f203d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -55,8 +55,8 @@
 
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
-        public void onTaskLaunching();
-        public void onLastTaskRemoved();
+        public void onTaskViewClicked();
+        public void onAllTaskViewsDismissed();
         public void onExitToHomeAnimationTriggered();
     }
 
@@ -66,8 +66,6 @@
 
     // The space partitioning root of this container
     SpaceNode mBSP;
-    // Whether there are any tasks
-    boolean mHasTasks;
     // Search bar view
     View mSearchBar;
     // Recents view callbacks
@@ -100,15 +98,21 @@
     public void setBSP(SpaceNode n) {
         mBSP = n;
 
+        // Remove all TaskStackViews (but leave the search bar)
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            View v = getChildAt(i);
+            if (v != mSearchBar) {
+                removeViewAt(i);
+            }
+        }
+
         // Create and add all the stacks for this partition of space.
-        mHasTasks = false;
-        removeAllViews();
         ArrayList<TaskStack> stacks = mBSP.getStacks();
         for (TaskStack stack : stacks) {
             TaskStackView stackView = new TaskStackView(getContext(), stack);
             stackView.setCallbacks(this);
             addView(stackView);
-            mHasTasks |= (stack.getTaskCount() > 0);
         }
 
         // Enable debug mode drawing
@@ -127,7 +131,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 TaskStackView stackView = (TaskStackView) child;
                 TaskStack stack = stackView.mStack;
                 // Iterate the stack views and try and find the focused task
@@ -140,7 +144,7 @@
                             Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
                                     "Found focused Task");
                         }
-                        onTaskLaunched(stackView, tv, stack, task);
+                        onTaskViewClicked(stackView, tv, stack, task);
                         return true;
                     }
                 }
@@ -159,7 +163,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 TaskStackView stackView = (TaskStackView) child;
                 TaskStack stack = stackView.mStack;
                 ArrayList<Task> tasks = stack.getTasks();
@@ -176,7 +180,7 @@
                             tv = stv;
                         }
                     }
-                    onTaskLaunched(stackView, tv, stack, task);
+                    onTaskViewClicked(stackView, tv, stack, task);
                     return true;
                 }
             }
@@ -193,7 +197,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 TaskStackView stackView = (TaskStackView) child;
                 stackView.startEnterRecentsAnimation(ctx);
             }
@@ -213,7 +217,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 TaskStackView stackView = (TaskStackView) child;
                 stackView.startExitToHomeAnimation(ctx);
             }
@@ -238,7 +242,6 @@
             // Add the new search bar
             if (searchBar != null) {
                 mSearchBar = searchBar;
-                mSearchBar.setVisibility(mHasTasks ? View.VISIBLE : View.GONE);
                 addView(mSearchBar);
 
                 if (Console.Enabled) {
@@ -250,6 +253,18 @@
         }
     }
 
+    /** Returns whether there is currently a search bar */
+    public boolean hasSearchBar() {
+        return mSearchBar != null;
+    }
+
+    /** Sets the visibility of the search bar */
+    public void setSearchBarVisibility(int visibility) {
+        if (mSearchBar != null) {
+            mSearchBar.setVisibility(visibility);
+        }
+    }
+
     /**
      * This is called with the full size of the window since we are handling our own insets.
      */
@@ -289,7 +304,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView && child.getVisibility() != GONE) {
+            if (child != mSearchBar && child.getVisibility() != GONE) {
                 child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode),
                         MeasureSpec.makeMeasureSpec(childHeight, heightMode));
             }
@@ -333,7 +348,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView && child.getVisibility() != GONE) {
+            if (child != mSearchBar && child.getVisibility() != GONE) {
                 TaskStackView tsv = (TaskStackView) child;
                 child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight());
             }
@@ -370,7 +385,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 stackView = (TaskStackView) child;
                 stackView.onUserInteraction();
             }
@@ -384,7 +399,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 stackView = (TaskStackView) child;
                 break;
             }
@@ -415,11 +430,11 @@
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @Override
-    public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
-                               final TaskStack stack, final Task task) {
+    public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
+                                  final TaskStack stack, final Task task) {
         // Notify any callbacks of the launching of a new task
         if (mCb != null) {
-            mCb.onTaskLaunching();
+            mCb.onTaskViewClicked();
         }
 
         // Upfront the processing of the thumbnail
@@ -491,7 +506,7 @@
                     }
 
                     // And clean up the old task
-                    onTaskRemoved(task);
+                    onTaskViewDismissed(task);
                 }
 
                 if (Console.Enabled) {
@@ -515,7 +530,7 @@
     }
 
     @Override
-    public void onTaskAppInfoLaunched(Task t) {
+    public void onTaskViewAppInfoClicked(Task t) {
         // Create a new task stack with the application info details activity
         Intent baseIntent = t.key.baseIntent;
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
@@ -526,9 +541,9 @@
     }
 
     @Override
-    public void onTaskRemoved(Task t) {
+    public void onTaskViewDismissed(Task t) {
         // Remove any stored data from the loader.  We currently don't bother notifying the views
-        // that the data has been unloaded because at the point we call onTaskRemoved(), the views
+        // that the data has been unloaded because at the point we call onTaskViewDismissed(), the views
         // either don't need to be updated, or have already been removed.
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         loader.deleteTaskData(t, false);
@@ -542,8 +557,8 @@
     }
 
     @Override
-    public void onLastTaskRemoved() {
-        mCb.onLastTaskRemoved();
+    public void onAllTaskViewsDismissed() {
+        mCb.onAllTaskViewsDismissed();
     }
 
     @Override
@@ -582,7 +597,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child instanceof TaskStackView) {
+            if (child != mSearchBar) {
                 TaskStackView stackView = (TaskStackView) child;
                 stackView.onComponentRemoved(cns);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index 2c0dea3..9c60603 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -92,7 +92,6 @@
     public boolean onTouchEvent(MotionEvent event) {
         // We ignore taps on the task bar except on the filter and dismiss buttons
         if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
-        if (mConfig.debugModeEnabled) return true;
 
         return super.onTouchEvent(event);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 2c05daa..dd47fddf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -53,10 +53,10 @@
 
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
-        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
-        public void onTaskAppInfoLaunched(Task t);
-        public void onTaskRemoved(Task t);
-        public void onLastTaskRemoved();
+        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+        public void onTaskViewAppInfoClicked(Task t);
+        public void onTaskViewDismissed(Task t);
+        public void onAllTaskViewsDismissed();
         public void onTaskStackFilterTriggered();
         public void onTaskStackUnfilterTriggered();
     }
@@ -69,7 +69,7 @@
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
     ViewPool<TaskView, Task> mViewPool;
-    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
+    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
     DozeTrigger mUIDozeTrigger;
 
     // The virtual stack scroll that we use for the card layout
@@ -205,12 +205,13 @@
                                        int stackScroll,
                                        int[] visibleRangeOut,
                                        boolean boundTranslationsToRect) {
-        // XXX: Optimization: Use binary search to find the visible range
+        // XXX: We should be intelligent about where to look for the visible stack range using the
+        //      current stack scroll.
 
         int taskTransformCount = taskTransforms.size();
         int taskCount = tasks.size();
-        int firstVisibleIndex = -1;
-        int lastVisibleIndex = -1;
+        int frontMostVisibleIndex = -1;
+        int backMostVisibleIndex = -1;
 
         // We can reuse the task transforms where possible to reduce object allocation
         if (taskTransformCount < taskCount) {
@@ -224,14 +225,24 @@
         }
 
         // Update the stack transforms
-        for (int i = 0; i < taskCount; i++) {
+        for (int i = taskCount - 1; i >= 0; i--) {
             TaskViewTransform transform = mStackAlgorithm.getStackTransform(i, stackScroll,
                     taskTransforms.get(i));
             if (transform.visible) {
-                if (firstVisibleIndex < 0) {
-                    firstVisibleIndex = i;
+                if (frontMostVisibleIndex < 0) {
+                    frontMostVisibleIndex = i;
                 }
-                lastVisibleIndex = i;
+                backMostVisibleIndex = i;
+            } else {
+                if (backMostVisibleIndex != -1) {
+                    // We've reached the end of the visible range, so going down the rest of the
+                    // stack, we can just reset the transforms accordingly
+                    while (i >= 0) {
+                        taskTransforms.get(i).reset();
+                        i--;
+                    }
+                    break;
+                }
             }
 
             if (boundTranslationsToRect) {
@@ -240,8 +251,8 @@
             }
         }
         if (visibleRangeOut != null) {
-            visibleRangeOut[0] = firstVisibleIndex;
-            visibleRangeOut[1] = lastVisibleIndex;
+            visibleRangeOut[0] = frontMostVisibleIndex;
+            visibleRangeOut[1] = backMostVisibleIndex;
         }
     }
 
@@ -275,13 +286,13 @@
             int[] visibleRange = mTmpVisibleRange;
             int stackScroll = getStackScroll();
             ArrayList<Task> tasks = mStack.getTasks();
-            updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false);
+            updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
 
             // Update the visible state of all the tasks
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
                 Task task = tasks.get(i);
-                TaskViewTransform transform = mTaskTransforms.get(i);
+                TaskViewTransform transform = mCurrentTaskTransforms.get(i);
                 TaskView tv = getChildViewForTask(task);
 
                 if (transform.visible) {
@@ -290,8 +301,9 @@
                         // When we are picking up a new view from the view pool, prepare it for any
                         // following animation by putting it in a reasonable place
                         if (mStackViewsAnimationDuration > 0 && i != 0) {
-                            int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
-                                    (visibleRange[1] + 1);
+                            int fromIndex = (transform.t < 0) ?
+                                    Math.max(0, (visibleRange[1] - 1)) :
+                                    Math.min(taskCount - 1, (visibleRange[0] + 1));
                             tv.updateViewPropertiesToTaskTransform(
                                     mStackAlgorithm.getStackTransform(fromIndex, stackScroll), 0);
                         }
@@ -310,10 +322,10 @@
                 TaskView tv = (TaskView) getChildAt(i);
                 Task task = tv.getTask();
                 int taskIndex = mStack.indexOfTask(task);
-                if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) {
+                if (taskIndex < 0 || !mCurrentTaskTransforms.get(taskIndex).visible) {
                     mViewPool.returnViewToPool(tv);
                 } else {
-                    tv.updateViewPropertiesToTaskTransform(mTaskTransforms.get(taskIndex),
+                    tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
                             mStackViewsAnimationDuration);
                 }
             }
@@ -769,9 +781,6 @@
             if (mConfig.launchedWithAltTab) {
                 // When alt-tabbing, we focus the next previous task
                 focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
-            } else {
-                // Normally we just focus the front task
-                focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
             }
         }
     }
@@ -864,7 +873,7 @@
         }
 
         // Notify the callback that we've removed the task and it can clean up after it
-        mCb.onTaskRemoved(t);
+        mCb.onTaskViewDismissed(t);
 
         // Update the min/max scroll and animate other task views into their new positions
         updateMinMaxScroll(true);
@@ -880,7 +889,7 @@
                 shouldFinishActivity = (mStack.getTaskCount() == 0);
             }
             if (shouldFinishActivity) {
-                mCb.onLastTaskRemoved();
+                mCb.onAllTaskViewsDismissed();
             }
         }
     }
@@ -1033,7 +1042,7 @@
     /**** TaskViewCallbacks Implementation ****/
 
     @Override
-    public void onTaskIconClicked(TaskView tv) {
+    public void onTaskViewAppIconClicked(TaskView tv) {
         if (Console.Enabled) {
             Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
                     tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
@@ -1049,19 +1058,14 @@
     }
 
     @Override
-    public void onTaskAppInfoClicked(TaskView tv) {
+    public void onTaskViewAppInfoClicked(TaskView tv) {
         if (mCb != null) {
-            mCb.onTaskAppInfoLaunched(tv.getTask());
+            mCb.onTaskViewAppInfoClicked(tv.getTask());
         }
     }
 
     @Override
-    public void onTaskFocused(TaskView tv) {
-        // Do nothing
-    }
-
-    @Override
-    public void onTaskDismissed(TaskView tv) {
+    public void onTaskViewDismissed(TaskView tv) {
         Task task = tv.getTask();
         // Remove the task from the view
         mStack.removeTask(task);
@@ -1082,7 +1086,7 @@
         mUIDozeTrigger.stopDozing();
 
         if (mCb != null) {
-            mCb.onTaskLaunched(this, tv, mStack, task);
+            mCb.onTaskViewClicked(this, tv, mStack, task);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 51f994e..304d45c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -400,16 +400,11 @@
         // Re-enable clipping with the stack (we will reuse this view)
         tv.setClipViewInStack(true);
         // Remove the task view from the stack
-        mSv.onTaskDismissed(tv);
+        mSv.onTaskViewDismissed(tv);
     }
 
     @Override
     public void onSnapBackCompleted(View v) {
-        onDragCancelled(v);
-    }
-
-    @Override
-    public void onDragCancelled(View v) {
         TaskView tv = (TaskView) v;
         // Disable HW layers on that task
         if (mSv.mHwLayersTrigger.getCount() == 0) {
@@ -420,4 +415,9 @@
         // Re-enable touch events from this task view
         mSv.setTouchOnTaskView(tv, true);
     }
+
+    @Override
+    public void onDragCancelled(View v) {
+        // Do nothing
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index c6bacbd..6b06945 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -47,10 +47,9 @@
         View.OnLongClickListener {
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
-        public void onTaskIconClicked(TaskView tv);
-        public void onTaskAppInfoClicked(TaskView tv);
-        public void onTaskFocused(TaskView tv);
-        public void onTaskDismissed(TaskView tv);
+        public void onTaskViewAppIconClicked(TaskView tv);
+        public void onTaskViewAppInfoClicked(TaskView tv);
+        public void onTaskViewDismissed(TaskView tv);
     }
 
     RecentsConfiguration mConfig;
@@ -63,8 +62,6 @@
     boolean mTaskDataLoaded;
     boolean mIsFocused;
     boolean mClipViewInStack;
-    Point mLastTouchDown = new Point();
-    Path mRoundedRectClipPath = new Path();
     Rect mTmpRect = new Rect();
     Paint mLayerPaint = new Paint();
 
@@ -110,6 +107,7 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
+        setClipToOutline(true);
         setDim(getDim());
     }
 
@@ -133,30 +131,13 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        // Update the rounded rect clip path
-        float radius = mConfig.taskViewRoundedCornerRadiusPx;
-        mRoundedRectClipPath.reset();
-        mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
-                radius, radius, Path.Direction.CW);
-
         // Update the outline
         Outline o = new Outline();
         o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() -
-                mConfig.taskViewShadowOutlineBottomInsetPx, radius);
+                mConfig.taskViewShadowOutlineBottomInsetPx, mConfig.taskViewRoundedCornerRadiusPx);
         setOutline(o);
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                mLastTouchDown.set((int) ev.getX(), (int) ev.getY());
-                break;
-        }
-        return super.onInterceptTouchEvent(ev);
-    }
-
     /** Set callback */
     void setCallbacks(TaskViewCallbacks cb) {
         mCb = cb;
@@ -291,7 +272,9 @@
         } else if (mConfig.launchedFromHome) {
             // Move the task view off screen (below) so we can animate it in
             setTranslationY(offscreenY);
-            setTranslationZ(0);
+            if (Constants.DebugFlags.App.EnableShadows) {
+                setTranslationZ(0);
+            }
             setScaleX(1f);
             setScaleY(1f);
         }
@@ -365,11 +348,13 @@
             int frontIndex = (ctx.stackViewCount - ctx.stackViewIndex - 1);
             int delay = mConfig.taskBarEnterAnimDelay +
                     frontIndex * mConfig.taskViewEnterFromHomeDelay;
+            if (Constants.DebugFlags.App.EnableShadows) {
+                animate().translationZ(transform.translationZ);
+            }
             animate()
                     .scaleX(transform.scale)
                     .scaleY(transform.scale)
                     .translationY(transform.translationY)
-                    .translationZ(transform.translationZ)
                     .setStartDelay(delay)
                     .setUpdateListener(null)
                     .setInterpolator(mConfig.quintOutInterpolator)
@@ -385,6 +370,9 @@
                     })
                     .start();
             ctx.postAnimationTrigger.increment();
+        } else {
+            // Otherwise, just enable the thumbnail clip
+            mEnableThumbnailClip.run();
         }
     }
 
@@ -527,11 +515,7 @@
 
     @Override
     public void draw(Canvas canvas) {
-        int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-        // Apply the rounded rect clip path on the whole view
-        canvas.clipPath(mRoundedRectClipPath);
         super.draw(canvas);
-        canvas.restoreToCount(restoreCount);
 
         // Apply the dim if necessary
         if (mDim > 0) {
@@ -553,7 +537,6 @@
         requestFocus();
         setFocusableInTouchMode(false);
         invalidate();
-        mCb.onTaskFocused(this);
     }
 
     /**
@@ -626,13 +609,13 @@
             @Override
             public void run() {
                 if (v == mBarView.mApplicationIcon) {
-                    mCb.onTaskIconClicked(tv);
+                    mCb.onTaskViewAppIconClicked(tv);
                 } else if (v == mBarView.mDismissButton) {
                     // Animate out the view and call the callback
                     startDeleteTaskAnimation(new Runnable() {
                         @Override
                         public void run() {
-                            mCb.onTaskDismissed(tv);
+                            mCb.onTaskViewDismissed(tv);
                         }
                     });
                 }
@@ -643,7 +626,7 @@
     @Override
     public boolean onLongClick(View v) {
         if (v == mBarView.mApplicationIcon) {
-            mCb.onTaskAppInfoClicked(this);
+            mCb.onTaskViewAppInfoClicked(this);
             return true;
         }
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 1550217..c02a598 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -94,11 +94,7 @@
     private boolean mDark;
     private final Paint mDarkPaint = createDarkPaint();
 
-    private int mBgResId = com.android.internal.R.drawable.notification_material_bg;
-    private int mDimmedBgResId = com.android.internal.R.drawable.notification_material_bg_dim;
-
     private int mBgTint = 0;
-    private int mDimmedBgTint = 0;
     private final int mRoundedRectCornerRadius;
 
     /**
@@ -133,6 +129,11 @@
     private ValueAnimator mAppearAnimator;
     private float mAppearAnimationFraction = -1.0f;
     private float mAppearAnimationTranslation;
+    private boolean mShowingLegacyBackground;
+    private final int mLegacyColor;
+    private final int mNormalColor;
+    private final int mLowPriorityColor;
+    private boolean mIsBelowSpeedBump;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -148,7 +149,11 @@
         setClipToPadding(false);
         mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
         mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.notification_material_rounded_rect_radius);
+                R.dimen.notification_material_rounded_rect_radius);
+        mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
+        mNormalColor = getResources().getColor(R.color.notification_material_background_color);
+        mLowPriorityColor = getResources().getColor(
+                R.color.notification_material_background_low_priority_color);
     }
 
     @Override
@@ -156,9 +161,12 @@
         super.onFinishInflate();
         mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
         mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
+        mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
+        mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
         updateBackground();
-        updateBackgroundResources();
+        updateBackgroundTint();
         mScrimView = (NotificationScrimView) findViewById(R.id.scrim_view);
+        setScrimAmount(0);
     }
 
     private final Runnable mTapTimeoutRunnable = new Runnable() {
@@ -177,6 +185,13 @@
         }
     }
 
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        if (!mDimmed){
+            mBackgroundNormal.drawableHotspotChanged(x, y);
+        }
+    }
+
     private boolean handleTouchEventDimmed(MotionEvent event) {
         int action = event.getActionMasked();
         switch (action) {
@@ -327,22 +342,36 @@
         return p;
     }
 
-    /**
-     * Sets the resource id for the background of this notification.
-     *
-     * @param bgResId The background resource to use in normal state.
-     * @param dimmedBgResId The background resource to use in dimmed state.
-     */
-    public void setBackgroundResourceIds(int bgResId, int bgTint, int dimmedBgResId, int dimmedTint) {
-        mBgResId = bgResId;
-        mBgTint = bgTint;
-        mDimmedBgResId = dimmedBgResId;
-        mDimmedBgTint = dimmedTint;
-        updateBackgroundResources();
+    public void setShowingLegacyBackground(boolean showing) {
+        mShowingLegacyBackground = showing;
+        updateBackgroundTint();
     }
 
-    public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) {
-        setBackgroundResourceIds(bgResId, 0, dimmedBgResId, 0);
+    @Override
+    public void setBelowSpeedBump(boolean below) {
+        super.setBelowSpeedBump(below);
+        if (below != mIsBelowSpeedBump) {
+            mIsBelowSpeedBump = below;
+            updateBackgroundTint();
+        }
+    }
+
+    /**
+     * Sets the tint color of the background
+     */
+    public void setTintColor(int color) {
+        mBgTint = color;
+        updateBackgroundTint();
+    }
+
+    private void updateBackgroundTint() {
+        int color = getBackgroundColor();
+        if (color == mNormalColor) {
+            // We don't need to tint a normal notification
+            color = 0;
+        }
+        mBackgroundDimmed.setTint(color);
+        mBackgroundNormal.setTint(color);
     }
 
     private void fadeBackground() {
@@ -396,11 +425,6 @@
         }
     }
 
-    private void updateBackgroundResources() {
-        mBackgroundDimmed.setCustomBackground(mDimmedBgResId, mDimmedBgTint);
-        mBackgroundNormal.setCustomBackground(mBgResId, mBgTint);
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -575,8 +599,15 @@
     }
 
     private int getBackgroundColor() {
-        // TODO: get real color
-        return 0xfffafafa;
+        if (mBgTint != 0) {
+            return mBgTint;
+        } else if (mShowingLegacyBackground) {
+            return mLegacyColor;
+        } else if (mIsBelowSpeedBump) {
+            return mLowPriorityColor;
+        } else {
+            return mNormalColor;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3690701..df3e25d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -562,20 +562,14 @@
         if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) {
             // Using custom RemoteViews
             if (version >= Build.VERSION_CODES.GINGERBREAD && version < Build.VERSION_CODES.L) {
-                entry.row.setBackgroundResourceIds(
-                        com.android.internal.R.drawable.notification_bg,
-                        com.android.internal.R.drawable.notification_bg_dim);
+                entry.row.setShowingLegacyBackground(true);
                 entry.legacy = true;
             }
         } else {
             // Using platform templates
             final int color = sbn.getNotification().color;
             if (isMediaNotification(entry)) {
-                entry.row.setBackgroundResourceIds(
-                        com.android.internal.R.drawable.notification_material_bg,
-                        color,
-                        com.android.internal.R.drawable.notification_material_bg_dim,
-                        color);
+                entry.row.setTintColor(color);
             }
         }
     }
@@ -903,15 +897,15 @@
     protected void onShowSearchPanel() {
     }
 
-    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
             return inflateViews(entry, parent, false);
     }
 
-    public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
+    protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
             return inflateViews(entry, parent, true);
     }
 
-    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
+    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
         int maxHeight = mRowMaxHeight;
         StatusBarNotification sbn = entry.notification;
         RemoteViews contentView = sbn.getNotification().contentView;
@@ -933,11 +927,30 @@
 
         Notification publicNotification = sbn.getNotification().publicVersion;
 
-        // create the row view
-        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
-                R.layout.status_bar_notification_row, parent, false);
+        ExpandableNotificationRow row;
+
+        // Stash away previous user expansion state so we can restore it at
+        // the end.
+        boolean hasUserChangedExpansion = false;
+        boolean userExpanded = false;
+        boolean userLocked = false;
+
+        if (entry.row != null) {
+            row = entry.row;
+            hasUserChangedExpansion = row.hasUserChangedExpansion();
+            userExpanded = row.isUserExpanded();
+            userLocked = row.isUserLocked();
+            row.reset();
+            if (hasUserChangedExpansion) {
+                row.setUserExpanded(userExpanded);
+            }
+        } else {
+            // create the row view
+            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
+                    parent, false);
+        }
 
         // for blaming (see SwipeHelper.setLongPressListener)
         row.setTag(sbn.getPackageName());
@@ -1077,13 +1090,20 @@
         entry.row = row;
         entry.row.setHeightRange(mRowMinHeight, maxHeight);
         entry.row.setOnActivatedListener(this);
-        entry.row.setIsBelowSpeedBump(isBelowSpeedBump(entry.notification));
         entry.expanded = contentViewLocal;
         entry.expandedPublic = publicViewLocal;
         entry.setBigContentView(bigContentViewLocal);
 
         applyLegacyRowBackground(sbn, entry);
 
+        // Restore previous flags.
+        if (hasUserChangedExpansion) {
+            // Note: setUserExpanded() conveniently ignores calls with
+            //       userExpanded=true if !isExpandable().
+            row.setUserExpanded(userExpanded);
+        }
+        row.setUserLocked(userLocked);
+
         return true;
     }
 
@@ -1324,12 +1344,13 @@
             RankingMap ranking);
     protected abstract void updateNotificationRanking(RankingMap ranking);
     public abstract void removeNotification(String key, RankingMap ranking);
+
     public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
         if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
 
         final String key = notification.getKey();
         boolean wasHeadsUp = isHeadsUp(key);
-        NotificationData.Entry oldEntry;
+        Entry oldEntry;
         if (wasHeadsUp) {
             oldEntry = mHeadsUpNotificationView.getEntry();
         } else {
@@ -1370,8 +1391,7 @@
                     + " publicView=" + publicContentView);
         }
 
-        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
-        // didn't change.
+        // Can we just reapply the RemoteViews in place?
 
         // 1U is never null
         boolean contentsUnchanged = oldEntry.expanded != null
@@ -1484,15 +1504,9 @@
                     addNotification(notification, ranking);  //this will pop the headsup
                 } else {
                     if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
-                    removeNotificationViews(key, ranking);
-                    addNotificationViews(notification, ranking);
-                    final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
-                    final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion();
-                    if (userChangedExpansion) {
-                        boolean userExpanded = oldEntry.row.isUserExpanded();
-                        newEntry.row.setUserExpanded(userExpanded);
-                        newEntry.row.notifyHeightChanged();
-                    }
+                    oldEntry.notification = notification;
+                    inflateViews(oldEntry, mStackScroller, wasHeadsUp);
+                    updateNotifications();
                 }
             }
         }
@@ -1554,19 +1568,9 @@
         } else {
             entry.row.setOnClickListener(null);
         }
-        boolean wasBelow = entry.row.isBelowSpeedBump();
-        boolean nowBelow = isBelowSpeedBump(notification);
-        if (wasBelow != nowBelow) {
-            entry.row.setIsBelowSpeedBump(nowBelow);
-        }
         entry.row.notifyContentUpdated();
     }
 
-    private boolean isBelowSpeedBump(StatusBarNotification notification) {
-        return notification.getNotification().priority ==
-                Notification.PRIORITY_MIN;
-    }
-
     protected void notifyHeadsUpScreenOn(boolean screenOn) {
         if (!screenOn) {
             scheduleHeadsUpEscalation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5981898..280bade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -52,13 +52,29 @@
     private NotificationContentView mPublicLayout;
     private NotificationContentView mPrivateLayout;
     private int mMaxExpandHeight;
-    private boolean mIsBelowSpeedBump;
     private View mVetoButton;
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
+    /**
+     * Resets this view so it can be re-used for an updated notification.
+     */
+    public void reset() {
+        mRowMinHeight = 0;
+        mRowMaxHeight = 0;
+        mExpandable = false;
+        mHasUserChangedExpansion = false;
+        mUserLocked = false;
+        mShowingPublic = false;
+        mIsSystemExpanded = false;
+        mExpansionDisabled = false;
+        mPublicLayout.reset();
+        mPrivateLayout.reset();
+        mMaxExpandHeight = 0;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -111,6 +127,7 @@
      * @param userExpanded whether the user wants this notification to be expanded
      */
     public void setUserExpanded(boolean userExpanded) {
+        if (userExpanded && !mExpandable) return;
         mHasUserChangedExpansion = true;
         mUserExpanded = userExpanded;
     }
@@ -245,14 +262,6 @@
         mPublicLayout.setClipTopAmount(clipTopAmount);
     }
 
-    public boolean isBelowSpeedBump() {
-        return mIsBelowSpeedBump;
-    }
-
-    public void setIsBelowSpeedBump(boolean isBelow) {
-        this.mIsBelowSpeedBump = isBelow;
-    }
-
     public void notifyContentUpdated() {
         mPublicLayout.notifyContentUpdated();
         mPrivateLayout.notifyContentUpdated();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 0a5d138..b71cd77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -234,10 +234,18 @@
 
     public abstract void setScrimAmount(float scrimAmount);
 
+    public void setBelowSpeedBump(boolean below) {
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
     public interface OnHeightChangedListener {
+
+        /**
+         * @param view the view for which the height changed, or {@code null} if just the top
+         *             padding or the padding between the elements changed
+         */
         void onHeightChanged(ExpandableView view);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 1c2ca91..ad274b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -17,11 +17,15 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
+import com.android.systemui.R;
 
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
@@ -31,9 +35,15 @@
     private Drawable mBackground;
     private int mClipTopAmount;
     private int mActualHeight;
+    private final int mTintedRippleColor;
+    private final int mNormalRippleColor;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mTintedRippleColor = context.getResources().getColor(
+                R.color.notification_ripple_tinted_color);
+        mNormalRippleColor = context.getResources().getColor(
+                R.color.notification_ripple_untinted_color);
     }
 
     @Override
@@ -64,6 +74,13 @@
         }
     }
 
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        if (mBackground != null) {
+            mBackground.setHotspot(x, y);
+        }
+    }
+
     /**
      * Sets a background drawable. As we need to change our bounds independently of layout, we need
      * the notion of a background independently of the regular View background..
@@ -80,14 +97,27 @@
         invalidate();
     }
 
-    public void setCustomBackground(int drawableResId, int tintColor) {
-        final Drawable d = getResources().getDrawable(drawableResId);
-        if (tintColor != 0) {
-            d.setColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
-        }
+    public void setCustomBackground(int drawableResId) {
+        final Drawable d = mContext.getDrawable(drawableResId);
         setCustomBackground(d);
     }
 
+    public void setTint(int tintColor) {
+        int rippleColor;
+        if (tintColor != 0) {
+            mBackground.setColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
+            rippleColor = mTintedRippleColor;
+        } else {
+            mBackground.clearColorFilter();
+            rippleColor = mNormalRippleColor;
+        }
+        if (mBackground instanceof RippleDrawable) {
+            RippleDrawable ripple = (RippleDrawable) mBackground;
+            ripple.setColor(ColorStateList.valueOf(rippleColor));
+        }
+        invalidate();
+    }
+
     public void setActualHeight(int actualHeight) {
         mActualHeight = actualHeight;
         invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index f919501..f3aba0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -51,13 +51,12 @@
 
     private boolean mContractedVisible = true;
 
-    private Paint mFadePaint = new Paint();
+    private final Paint mFadePaint = new Paint();
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
-        mActualHeight = mSmallHeight;
         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+        reset();
     }
 
     @Override
@@ -66,6 +65,15 @@
         updateClipping();
     }
 
+    public void reset() {
+        removeAllViews();
+        mContractedChild = null;
+        mExpandedChild = null;
+        mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+        mActualHeight = mSmallHeight;
+        mContractedVisible = true;
+    }
+
     public void setContractedChild(View child) {
         if (mContractedChild != null) {
             removeView(mContractedChild);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index b2e79c9..be2c515 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -66,7 +66,7 @@
     private View mKeyguardStatusView;
     private ObservableScrollView mScrollView;
     private TextView mClockView;
-
+    private View mReserveNotificationSpace;
     private MirrorView mSystemIconsCopy;
 
     private NotificationStackScrollLayout mNotificationStackScroller;
@@ -89,8 +89,10 @@
      */
     private boolean mIntercepting;
     private boolean mQsExpanded;
+    private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private int mStatusBarState;
     private float mInitialHeightOnTouch;
     private float mInitialTouchX;
     private float mInitialTouchY;
@@ -112,7 +114,6 @@
 
     private Interpolator mFastOutSlowInInterpolator;
     private Interpolator mFastOutLinearInterpolator;
-    private Interpolator mLinearOutSlowInInterpolator;
     private ObjectAnimator mClockAnimator;
     private int mClockAnimationTarget = -1;
     private int mTopPaddingAdjustment;
@@ -125,7 +126,13 @@
     private boolean mBlockTouches;
     private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
     private int mNotificationScrimWaitDistance;
-    private boolean mOnNotificationsOnDown;
+
+    /**
+     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
+     * need to take this into account in our panel height calculation.
+     */
+    private int mScrollYOverride = -1;
+    private boolean mQsAnimatorExpand;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -148,14 +155,14 @@
         mClockView = (TextView) findViewById(R.id.clock_view);
         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
         mScrollView.setListener(this);
+        mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
         mNotificationStackScroller = (NotificationStackScrollLayout)
                 findViewById(R.id.notification_stack_scroller);
         mNotificationStackScroller.setOnHeightChangedListener(this);
         mNotificationStackScroller.setOverscrollTopChangedListener(this);
+        mNotificationStackScroller.setScrollView(mScrollView);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
                 android.R.interpolator.fast_out_slow_in);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
-                android.R.interpolator.linear_out_slow_in);
         mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
                 android.R.interpolator.fast_out_linear_in);
         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
@@ -283,13 +290,6 @@
         requestLayout();
     }
 
-    /**
-     * @return Whether Quick Settings are currently expanded.
-     */
-    public boolean isQsExpanded() {
-        return mQsExpanded;
-    }
-
     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
         mQsExpansionEnabled = qsExpansionEnabled;
     }
@@ -309,6 +309,18 @@
         setQsExpansion(mQsMinExpansionHeight);
     }
 
+    public void animateCloseQs() {
+        if (mQsExpansionAnimator != null) {
+            if (!mQsAnimatorExpand) {
+                return;
+            }
+            float height = mQsExpansionHeight;
+            mQsExpansionAnimator.cancel();
+            setQsExpansion(height);
+        }
+        flingSettings(0 /* vel */, false);
+    }
+
     public void openQs() {
         cancelAnimation();
         if (mQsExpansionEnabled) {
@@ -356,10 +368,16 @@
                 mInitialTouchX = x;
                 initVelocityTracker();
                 trackMovement(event);
-                mOnNotificationsOnDown = isOnNotifications(x, y);
                 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
                     getParent().requestDisallowInterceptTouchEvent(true);
                 }
+                if (mQsExpansionAnimator != null) {
+                    onQsExpansionStarted();
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mQsTracking = true;
+                    mIntercepting = false;
+                    mNotificationStackScroller.removeLongPressCallback();
+                }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
                 final int upPointer = event.getPointerId(event.getActionIndex());
@@ -404,8 +422,6 @@
                 if (mQsTracking) {
                     flingQsWithCurrentVelocity();
                     mQsTracking = false;
-                } else if (mQsFullyExpanded && mOnNotificationsOnDown) {
-                    flingSettings(0 /* vel */, false /* expand */);
                 }
                 mIntercepting = false;
                 break;
@@ -413,10 +429,6 @@
         return !mQsExpanded && super.onInterceptTouchEvent(event);
     }
 
-    private boolean isOnNotifications(float x, float y) {
-        return mNotificationStackScroller.getChildAtPosition(x, y) != null;
-    }
-
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
 
@@ -443,8 +455,9 @@
 
     private float getQsExpansionFraction() {
         return (mQsExpansionHeight - mQsMinExpansionHeight)
-                / (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+                / (getTempQsMaxExpansion() - mQsMinExpansionHeight);
     }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (mBlockTouches) {
@@ -550,6 +563,8 @@
                 if ((fraction != 0f || y >= mInitialTouchY)
                         && (fraction != 1f || y <= mInitialTouchY)) {
                     flingQsWithCurrentVelocity();
+                } else {
+                    mScrollYOverride = -1;
                 }
                 if (mVelocityTracker != null) {
                     mVelocityTracker.recycle();
@@ -570,7 +585,6 @@
         }
     }
 
-
     @Override
     public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
         cancelAnimation();
@@ -589,6 +603,7 @@
             public void run() {
                 mStackScrollerOverscrolling = false;
                 mQsExpansionFromOverscroll = false;
+                updateQsState();
             }
         });
     }
@@ -602,6 +617,9 @@
 
         // Reset scroll position and apply that position to the expanded height.
         float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
+        if (mScrollView.getScrollY() != 0) {
+            mScrollYOverride = mScrollView.getScrollY();
+        }
         mScrollView.scrollTo(0, 0);
         setQsExpansion(height);
     }
@@ -611,14 +629,19 @@
         if (changed) {
             mQsExpanded = expanded;
             updateQsState();
+            requestPanelHeightUpdate();
+            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
         }
     }
 
-    public void setKeyguardShowing(boolean keyguardShowing) {
+    public void setBarState(int statusBarState) {
+        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
+                || statusBarState == StatusBarState.SHADE_LOCKED;
         if (!mKeyguardShowing && keyguardShowing) {
             setQsTranslation(mQsExpansionHeight);
             mHeader.setTranslationY(0f);
         }
+        mStatusBarState = statusBarState;
         mKeyguardShowing = keyguardShowing;
         updateQsState();
     }
@@ -626,12 +649,12 @@
     private void updateQsState() {
         boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
         mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling);
-        mNotificationStackScroller.setEnabled(!mQsExpanded || mQsExpansionFromOverscroll);
+        mNotificationStackScroller.setScrollingEnabled(mStatusBarState != StatusBarState.KEYGUARD
+                && (!mQsExpanded || mQsExpansionFromOverscroll));
         mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
         mQsContainer.setVisibility(
                 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
         mScrollView.setTouchEnabled(mQsExpanded);
-        mNotificationStackScroller.setTouchEnabled(!mQsExpanded || mQsExpansionFromOverscroll);
     }
 
     private void setQsExpansion(float height) {
@@ -643,7 +666,7 @@
             setQsExpanded(false);
         }
         mQsExpansionHeight = height;
-        mHeader.setExpansion(height - mQsPeekHeight);
+        mHeader.setExpansion(getQsExpansionFraction());
         setQsTranslation(height);
         requestScrollerTopPaddingUpdate(false /* animate */);
         updateNotificationScrim(height);
@@ -702,6 +725,7 @@
     private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
         float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
         if (target == mQsExpansionHeight) {
+            mScrollYOverride = -1;
             if (onFinishRunnable != null) {
                 onFinishRunnable.run();
             }
@@ -718,6 +742,7 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                mScrollYOverride = -1;
                 mQsExpansionAnimator = null;
                 if (onFinishRunnable != null) {
                     onFinishRunnable.run();
@@ -726,6 +751,7 @@
         });
         animator.start();
         mQsExpansionAnimator = animator;
+        mQsAnimatorExpand = expand;
     }
 
     /**
@@ -794,16 +820,23 @@
 
     @Override
     protected int getMaxPanelHeight() {
+        int min = mStatusBarMinHeight;
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
                 && mNotificationStackScroller.getNotGoneChildCount() == 0) {
-            return (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
+            int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
                     * HEADER_RUBBERBAND_FACTOR);
+            min = Math.max(min, minHeight);
         }
-        // TODO: Figure out transition for collapsing when QS is open, adjust height here.
-        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
-        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
-                - mTopPaddingAdjustment;
-        maxHeight = Math.max(maxHeight, mStatusBarMinHeight);
+        int maxHeight;
+        if (mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+            maxHeight = (int) calculatePanelHeightQsExpanded();
+        } else {
+            int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
+            maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
+                    - mTopPaddingAdjustment;
+            maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
+        }
+        maxHeight = Math.max(maxHeight, min);
         return maxHeight;
     }
 
@@ -816,12 +849,59 @@
         if (!mQsExpanded) {
             positionClockAndNotifications();
         }
+        if (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+                && !mQsExpansionFromOverscroll) {
+            float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
+                    + mNotificationStackScroller.getMinStackHeight()
+                    + mNotificationStackScroller.getNotificationTopPadding();
+            float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+            float t = (expandedHeight - panelHeightQsCollapsed)
+                    / (panelHeightQsExpanded - panelHeightQsCollapsed);
+
+            setQsExpansion(mQsMinExpansionHeight
+                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+        }
         mNotificationStackScroller.setStackHeight(expandedHeight);
         updateHeader();
         updateUnlockIcon();
         updateNotificationTranslucency();
     }
 
+    /**
+     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
+     *         collapsing QS / the panel when QS was scrolled
+     */
+    private int getTempQsMaxExpansion() {
+        int qsTempMaxExpansion = mQsMaxExpansionHeight;
+        if (mScrollYOverride != -1) {
+            qsTempMaxExpansion -= mScrollYOverride;
+        }
+        return qsTempMaxExpansion;
+    }
+
+    private float calculatePanelHeightQsExpanded() {
+        float notificationHeight = mNotificationStackScroller.getHeight()
+                - mNotificationStackScroller.getEmptyBottomMargin()
+                - mNotificationStackScroller.getTopPadding();
+        float totalHeight = mQsMaxExpansionHeight + notificationHeight
+                + mNotificationStackScroller.getNotificationTopPadding();
+        if (totalHeight > mNotificationStackScroller.getHeight()) {
+            float fullyCollapsedHeight = mQsMaxExpansionHeight
+                    + mNotificationStackScroller.getMinStackHeight()
+                    + mNotificationStackScroller.getNotificationTopPadding()
+                    - getScrollViewScrollY();
+            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
+        }
+        return totalHeight;
+    }
+
+    private int getScrollViewScrollY() {
+        if (mScrollYOverride != -1) {
+            return mScrollYOverride;
+        } else {
+            return mScrollView.getScrollY();
+        }
+    }
     private void updateNotificationTranslucency() {
         float alpha = (mNotificationStackScroller.getNotificationsTopY()
                 + mNotificationStackScroller.getItemHeight())
@@ -931,6 +1011,10 @@
         super.onExpandingStarted();
         mNotificationStackScroller.onExpansionStarted();
         mIsExpanding = true;
+        mQsExpandedWhenExpandingStarted = mQsExpanded;
+        if (mQsExpanded) {
+            onQsExpansionStarted();
+        }
     }
 
     @Override
@@ -938,6 +1022,7 @@
         super.onExpandingFinished();
         mNotificationStackScroller.onExpansionStopped();
         mIsExpanding = false;
+        mScrollYOverride = -1;
         if (mExpandedHeight == 0f) {
             mHeader.setListening(false);
             mQsPanel.setListening(false);
@@ -1005,6 +1090,12 @@
 
     @Override
     public void onHeightChanged(ExpandableView view) {
+
+        // Block update if we are in quick settings and just the top padding changed
+        // (i.e. view == null).
+        if (view == null && mQsExpanded) {
+            return;
+        }
         requestPanelHeightUpdate();
     }
 
@@ -1012,6 +1103,7 @@
     public void onScrollChanged() {
         if (mQsExpanded) {
             requestScrollerTopPaddingUpdate(false /* animate */);
+            requestPanelHeightUpdate();
         }
     }
 
@@ -1128,4 +1220,30 @@
             return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
         }
     }
+
+    @Override
+    protected float getCannedFlingDurationFactor() {
+        if (mQsExpanded) {
+            return 0.7f;
+        } else {
+            return 0.6f;
+        }
+    }
+
+    @Override
+    protected boolean isTrackingBlocked() {
+        return mConflictingQsExpansionGesture && mQsExpanded;
+    }
+
+    public void notifyVisibleChildrenChanged() {
+        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
+            mReserveNotificationSpace.setVisibility(View.VISIBLE);
+        } else {
+            mReserveNotificationSpace.setVisibility(View.GONE);
+        }
+    }
+
+    public boolean isQsExpanded() {
+        return mQsExpanded;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
index ea5b309..45f3632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
@@ -31,6 +31,7 @@
     private int mLastOverscrollAmount;
     private boolean mDispatchingTouchEvent;
     private boolean mTouchEnabled = true;
+    private boolean mInTouchEvent;
 
     public ObservableScrollView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -49,7 +50,7 @@
     }
 
     public boolean isDispatchingTouchEvent() {
-        return mDispatchingTouchEvent;
+        return mDispatchingTouchEvent || mInTouchEvent;
     }
 
     private int getMaxScrollY() {
@@ -63,6 +64,22 @@
     }
 
     @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        mInTouchEvent = true;
+        boolean result = super.onTouchEvent(ev);
+        mInTouchEvent = false;
+        return result;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        mInTouchEvent = true;
+        boolean result = super.onInterceptTouchEvent(ev);
+        mInTouchEvent = false;
+        return result;
+    }
+
+    @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (!mTouchEnabled) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 7d5d99d..2c5ece6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -289,7 +289,7 @@
                     }
                     mJustPeeked = false;
                 }
-                if (!mJustPeeked && (!waitForTouchSlop || mTracking)) {
+                if (!mJustPeeked && (!waitForTouchSlop || mTracking) && !isTrackingBlocked()) {
                     setExpandedHeightInternal(newHeight);
                 }
 
@@ -475,7 +475,8 @@
 
             // Make it shorter if we run a canned animation
             if (vel == 0) {
-                animator.setDuration((long) (animator.getDuration() / 1.75f));
+                animator.setDuration((long)
+                        (animator.getDuration() * getCannedFlingDurationFactor()));
             }
         }
         animator.addListener(new AnimatorListenerAdapter() {
@@ -546,9 +547,12 @@
         float currentMaxPanelHeight = getMaxPanelHeight();
 
         // If the user isn't actively poking us, let's update the height
-        if (!mTracking && mHeightAnimator == null
-                && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight
-                && !mPeekPending && mPeekAnimator == null) {
+        if ((!mTracking || isTrackingBlocked())
+                && mHeightAnimator == null
+                && mExpandedHeight > 0
+                && currentMaxPanelHeight != mExpandedHeight
+                && !mPeekPending
+                && mPeekAnimator == null) {
             setExpandedHeight(currentMaxPanelHeight);
         }
     }
@@ -576,6 +580,12 @@
         notifyBarPanelExpansionChanged();
     }
 
+    /**
+     * @return true if the panel tracking should be temporarily blocked; this is used when a
+     *         conflicting gesture (opening QS) is happening
+     */
+    protected abstract boolean isTrackingBlocked();
+
     protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
 
     protected abstract void onHeightUpdated(float expandedHeight);
@@ -866,4 +876,6 @@
     public abstract void resetViews();
 
     protected abstract float getPeekHeight();
+
+    protected abstract float getCannedFlingDurationFactor();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 387f5a7..a6a8e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -32,7 +32,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
@@ -78,7 +77,6 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
@@ -101,7 +99,6 @@
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeService;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.qs.CircularClipper;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.ActivatableNotificationView;
@@ -717,14 +714,6 @@
         // Set up the quick settings tile panel
         mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
         if (mQSPanel != null) {
-            mQSPanel.setUtils(new CircularClipper.Utils() {
-                @Override
-                public ValueAnimator createRevealAnimator(View v, int centerX, int centerY,
-                        float startRadius, float endRadius) {
-                    return ViewAnimationUtils.createCircularReveal(v, centerX, centerY,
-                            startRadius, endRadius);
-                }
-            });
             final QSTileHost qsh = new QSTileHost(mContext, this,
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, null /*tethering*/,
@@ -1181,13 +1170,9 @@
                 ent.row.setShowingPublic(showingPublic);
                 if (ent.autoRedacted && ent.legacy) {
                     if (showingPublic) {
-                        ent.row.setBackgroundResourceIds(
-                                com.android.internal.R.drawable.notification_material_bg,
-                                com.android.internal.R.drawable.notification_material_bg_dim);
+                        ent.row.setShowingLegacyBackground(false);
                     } else {
-                        ent.row.setBackgroundResourceIds(
-                                com.android.internal.R.drawable.notification_bg,
-                                com.android.internal.R.drawable.notification_bg_dim);
+                        ent.row.setShowingLegacyBackground(true);
                     }
                 }
                 toShow.add(ent.row);
@@ -1319,6 +1304,12 @@
         }
     }
 
+    @Override
+    protected void updateRowStates() {
+        super.updateRowStates();
+        mNotificationPanel.notifyVisibleChildrenChanged();
+    }
+
     protected void updateCarrierLabelVisibility(boolean force) {
         // TODO: Handle this for the notification stack scroller as well
         if (!mShowCarrierInPanel) return;
@@ -1691,9 +1682,6 @@
 
             mStatusBarWindow.cancelExpandHelper();
             mStatusBarView.collapseAllPanels(true);
-            if (isFlippedToSettings()) {
-                flipToNotifications(true /*animate*/);
-            }
         }
     }
 
@@ -1751,18 +1739,10 @@
         }
 
         mNotificationPanel.expand();
-        if (mStackScroller.getVisibility() != View.VISIBLE) {
-            flipToNotifications(true /*animate*/);
-        }
 
         if (false) postStartTracing();
     }
 
-    public void flipToNotifications(boolean animate) {
-        // TODO: Animation
-        mNotificationPanel.closeQs();
-    }
-
     @Override
     public void animateExpandSettingsPanel() {
         if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible);
@@ -1779,13 +1759,6 @@
         if (false) postStartTracing();
     }
 
-    public boolean isFlippedToSettings() {
-        if (mNotificationPanel != null) {
-            return mNotificationPanel.isQsExpanded();
-        }
-        return false;
-    }
-
     public void animateCollapseQuickSettings() {
         mStatusBarView.collapseAllPanels(true);
     }
@@ -2863,14 +2836,13 @@
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
             mHeader.setKeyguardShowing(true);
-            mNotificationPanel.setKeyguardShowing(true);
             mScrimController.setKeyguardShowing(true);
         } else {
             mKeyguardBottomArea.setVisibility(View.GONE);
             mHeader.setKeyguardShowing(false);
-            mNotificationPanel.setKeyguardShowing(false);
             mScrimController.setKeyguardShowing(false);
         }
+        mNotificationPanel.setBarState(mState);
         updateDozingState();
         updateStackScrollerState();
         updatePublicMode();
@@ -2905,7 +2877,6 @@
         mStackScroller.setDimmed(onKeyguard, false /* animate */);
         mStackScroller.setVisibility(!mShowLockscreenNotifications && onKeyguard
                 ? View.INVISIBLE : View.VISIBLE);
-        mStackScroller.setScrollingEnabled(!onKeyguard);
         mStackScroller.setExpandingEnabled(!onKeyguard);
         ActivatableNotificationView activatedChild = mStackScroller.getActivatedChild();
         mStackScroller.setActivatedChild(null);
@@ -2930,9 +2901,20 @@
 
     public boolean onBackPressed() {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            return mStatusBarKeyguardViewManager.onBackPressed();
+            if (mStatusBarKeyguardViewManager.onBackPressed()) {
+                return true;
+            }
+            if (mNotificationPanel.isQsExpanded()) {
+                mNotificationPanel.animateCloseQs();
+                return true;
+            }
+            return false;
         } else {
-            animateCollapsePanels();
+            if (mNotificationPanel.isQsExpanded()) {
+                mNotificationPanel.animateCloseQs();
+            } else {
+                animateCollapsePanels();
+            }
             return true;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 60f38b5..04b1443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -65,7 +65,6 @@
     private final CurrentUserTracker mUserTracker;
     private final VolumeComponent mVolume;
     private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
-    private final int mFeedbackStartDelay;
     private final FlashlightController mFlashlight;
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
@@ -110,7 +109,6 @@
             }
         };
         mUserTracker.startTracking();
-        mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
     }
 
     @Override
@@ -120,7 +118,7 @@
 
     @Override
     public void startSettingsActivity(final Intent intent) {
-        mStatusBar.postStartSettingsActivity(intent, mFeedbackStartDelay);
+        mStatusBar.postStartSettingsActivity(intent, 0);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 3e2dcef..2119316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Outline;
@@ -26,10 +28,12 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
+import android.widget.Switch;
 import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QSTile;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSlider;
 import com.android.systemui.statusbar.policy.UserInfoController;
@@ -60,6 +64,7 @@
     private View mSignalCluster;
     private View mSettingsButton;
     private View mBrightnessContainer;
+    private View mQsDetailHeader;
     private View mEmergencyCallsOnly;
     private TextView mChargingInfo;
 
@@ -104,6 +109,8 @@
         mBrightnessController = new BrightnessController(getContext(),
                 (ImageView) findViewById(R.id.brightness_icon),
                 (ToggleSlider) findViewById(R.id.brightness_slider));
+        mQsDetailHeader = findViewById(R.id.qs_detail_header);
+        mQsDetailHeader.setAlpha(0);
         mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only);
         mChargingInfo = (TextView) findViewById(R.id.header_charging_info);
         loadDimens();
@@ -219,6 +226,7 @@
         mDate.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
         mSettingsButton.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE);
         mBrightnessContainer.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
+        mQsDetailHeader.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
         if (mStatusIcons != null) {
             mStatusIcons.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
         }
@@ -300,8 +308,8 @@
         }
     }
 
-    public void setExpansion(float height) {
-        height = (height - mCollapsedHeight) * EXPANSION_RUBBERBAND_FACTOR + mCollapsedHeight;
+    public void setExpansion(float t) {
+        float height = mCollapsedHeight + t * (mExpandedHeight - mCollapsedHeight);
         if (height < mCollapsedHeight) {
             height = mCollapsedHeight;
         }
@@ -375,13 +383,6 @@
         }
     }
 
-    private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
-        @Override
-        public void onShowingDetail(boolean showingDetail) {
-            mBrightnessContainer.animate().alpha(showingDetail ? 0 : 1).withLayer().start();
-        }
-    };
-
     @Override
     public boolean shouldDelayChildPressedState() {
         return true;
@@ -418,4 +419,69 @@
     public boolean hasOverlappingRendering() {
         return !mKeyguardShowing || mExpanded;
     }
+
+    private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
+        @Override
+        public void onToggleStateChanged(final boolean state) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    handleToggleStateChanged(state);
+                }
+            });
+        }
+
+        @Override
+        public void onShowingDetail(final QSTile.DetailAdapter detail) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    handleShowingDetail(detail);
+                }
+            });
+        }
+
+        private void handleToggleStateChanged(boolean state) {
+            final Switch headerSwitch = (Switch)
+                    mQsDetailHeader.findViewById(android.R.id.toggle);
+            headerSwitch.setChecked(state);
+        }
+
+        private void handleShowingDetail(final QSTile.DetailAdapter detail) {
+            final boolean showingDetail = detail != null;
+            transition(mBrightnessContainer, !showingDetail);
+            transition(mQsDetailHeader, showingDetail);
+            if (showingDetail) {
+                final TextView headerTitle = (TextView)
+                        mQsDetailHeader.findViewById(android.R.id.title);
+                headerTitle.setText(detail.getTitle());
+                final Switch headerSwitch = (Switch)
+                        mQsDetailHeader.findViewById(android.R.id.toggle);
+                final Boolean toggleState = detail.getToggleState();
+                if (toggleState == null) {
+                    headerSwitch.setVisibility(INVISIBLE);
+                    mQsDetailHeader.setClickable(false);
+                } else {
+                    headerSwitch.setVisibility(VISIBLE);
+                    headerSwitch.setChecked(toggleState);
+                    mQsDetailHeader.setClickable(true);
+                    mQsDetailHeader.setOnClickListener(new OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            detail.setToggleState(!toggleState);
+                        }
+                    });
+                }
+            } else {
+                mQsDetailHeader.setClickable(false);
+            }
+        }
+
+        private void transition(final View v, final boolean in) {
+            if (in) {
+                v.bringToFront();
+            }
+            v.animate().alpha(in ? 1 : 0).withLayer().start();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 1f68860..7e113699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -33,4 +33,23 @@
                 String dataTypeContentDescriptionId, String description, boolean noSim);
         void onAirplaneModeChanged(boolean enabled);
     }
+
+    void addAccessPointCallback(AccessPointCallback callback);
+    void removeAccessPointCallback(AccessPointCallback callback);
+    void scanForAccessPoints();
+    void connect(AccessPoint ap);
+
+    public interface AccessPointCallback {
+        void onAccessPointsChanged(AccessPoint[] accessPoints);
+    }
+
+    public static class AccessPoint {
+        public static final int NO_NETWORK = -1;  // see WifiManager
+
+        public int networkId;
+        public int iconId;
+        public String ssid;
+        public boolean isConnected;
+        public int level;  // 0 - 5
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 4e54e41..4b94ebd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -169,6 +169,8 @@
         void setIsAirplaneMode(boolean is, int airplaneIcon);
     }
 
+    private final WifiAccessPointController mAccessPoints;
+
     /**
      * Construct this controller object and register for updates.
      */
@@ -237,6 +239,7 @@
         updateAirplaneMode();
 
         mLastLocale = mContext.getResources().getConfiguration().locale;
+        mAccessPoints = new WifiAccessPointController(mContext);
     }
 
     public boolean hasMobileDataFeature() {
@@ -282,6 +285,26 @@
     }
 
     @Override
+    public void addAccessPointCallback(AccessPointCallback callback) {
+        mAccessPoints.addCallback(callback);
+    }
+
+    @Override
+    public void removeAccessPointCallback(AccessPointCallback callback) {
+        mAccessPoints.removeCallback(callback);
+    }
+
+    @Override
+    public void scanForAccessPoints() {
+        mAccessPoints.scan();
+    }
+
+    @Override
+    public void connect(AccessPoint ap) {
+        mAccessPoints.connect(ap);
+    }
+
+    @Override
     public void setWifiEnabled(final boolean enabled) {
         new AsyncTask<Void, Void, Void>() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiAccessPointController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiAccessPointController.java
new file mode 100644
index 0000000..09e7472
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiAccessPointController.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.ActionListener;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPoint;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointCallback;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class WifiAccessPointController {
+    private static final String TAG = "WifiAccessPointController";
+    private static final boolean DEBUG = false;
+
+    private static final int[] ICONS = {
+        R.drawable.ic_qs_wifi_0,
+        R.drawable.ic_qs_wifi_full_1,
+        R.drawable.ic_qs_wifi_full_2,
+        R.drawable.ic_qs_wifi_full_3,
+        R.drawable.ic_qs_wifi_full_4,
+    };
+
+    private final Context mContext;
+    private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
+    private final WifiManager mWifiManager;
+    private final Receiver mReceiver = new Receiver();
+
+    private boolean mScanning;
+
+    public WifiAccessPointController(Context context) {
+        mContext = context;
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+    }
+
+    public void addCallback(AccessPointCallback callback) {
+        if (callback == null || mCallbacks.contains(callback)) return;
+        if (DEBUG) Log.d(TAG, "addCallback " + callback);
+        mCallbacks.add(callback);
+        mReceiver.setListening(!mCallbacks.isEmpty());
+    }
+
+    public void removeCallback(AccessPointCallback callback) {
+        if (callback == null) return;
+        if (DEBUG) Log.d(TAG, "removeCallback " + callback);
+        mCallbacks.remove(callback);
+        mReceiver.setListening(!mCallbacks.isEmpty());
+    }
+
+    public void scan() {
+        if (mScanning) return;
+        if (DEBUG) Log.d(TAG, "scan!");
+        mScanning = mWifiManager.startScan();
+    }
+
+    public void connect(AccessPoint ap) {
+        if (ap == null || ap.networkId < 0) return;
+        if (DEBUG) Log.d(TAG, "connect networkId=" + ap.networkId);
+        mWifiManager.connect(ap.networkId, new ActionListener() {
+            @Override
+            public void onSuccess() {
+                if (DEBUG) Log.d(TAG, "connect success");
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                if (DEBUG) Log.d(TAG, "connect failure reason=" + reason);
+            }
+        });
+    }
+
+    private void fireCallback(AccessPoint[] aps) {
+        for (AccessPointCallback callback : mCallbacks) {
+            callback.onAccessPointsChanged(aps);
+        }
+    }
+
+    private static String trimDoubleQuotes(String v) {
+        return v != null && v.length() >= 2 && v.charAt(0) == '\"'
+                && v.charAt(v.length() - 1) == '\"' ? v.substring(1, v.length() - 1) : v;
+    }
+
+    private int getConnectedNetworkId() {
+        final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        return wifiInfo != null ? wifiInfo.getNetworkId() : AccessPoint.NO_NETWORK;
+    }
+
+    private ArrayMap<String, WifiConfiguration> getConfiguredNetworksBySsid() {
+        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+        if (configs == null || configs.size() == 0) return ArrayMap.EMPTY;
+        final ArrayMap<String, WifiConfiguration> rt = new ArrayMap<String, WifiConfiguration>();
+        for (WifiConfiguration config : configs) {
+            rt.put(trimDoubleQuotes(config.SSID), config);
+        }
+        return rt;
+    }
+
+    private void updateAccessPoints() {
+        final int connectedNetworkId = getConnectedNetworkId();
+        if (DEBUG) Log.d(TAG, "connectedNetworkId: " + connectedNetworkId);
+        final List<ScanResult> scanResults = mWifiManager.getScanResults();
+        final ArrayMap<String, WifiConfiguration> configured = getConfiguredNetworksBySsid();
+        if (DEBUG) Log.d(TAG, "scanResults: " + scanResults);
+        final List<AccessPoint> aps = new ArrayList<AccessPoint>(scanResults.size());
+        final ArraySet<String> ssids = new ArraySet<String>();
+        for (ScanResult scanResult : scanResults) {
+            final String ssid = scanResult.SSID;
+            if (TextUtils.isEmpty(ssid) || ssids.contains(ssid)) continue;
+            if (!configured.containsKey(ssid)) continue;
+            ssids.add(ssid);
+            final WifiConfiguration config = configured.get(ssid);
+            final int level = WifiManager.calculateSignalLevel(scanResult.level, ICONS.length);
+            final AccessPoint ap = new AccessPoint();
+            ap.networkId = config != null ? config.networkId : AccessPoint.NO_NETWORK;
+            ap.ssid = ssid;
+            ap.iconId = ICONS[level];
+            ap.isConnected = ap.networkId != AccessPoint.NO_NETWORK
+                    && ap.networkId == connectedNetworkId;
+            ap.level = level;
+            aps.add(ap);
+        }
+        Collections.sort(aps, mByStrength);
+        fireCallback(aps.toArray(new AccessPoint[aps.size()]));
+    }
+
+    private final Comparator<AccessPoint> mByStrength = new Comparator<AccessPoint> () {
+        @Override
+        public int compare(AccessPoint lhs, AccessPoint rhs) {
+            return -Integer.compare(score(lhs), score(rhs));
+        }
+
+        private int score(AccessPoint ap) {
+            return ap.level + (ap.isConnected ? 10 : 0);
+        }
+    };
+
+    private final class Receiver extends BroadcastReceiver {
+        private boolean mRegistered;
+
+        public void setListening(boolean listening) {
+            if (listening && !mRegistered) {
+                if (DEBUG) Log.d(TAG, "Registering receiver");
+                final IntentFilter filter = new IntentFilter();
+                filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+                filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+                filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
+                filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+                filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+                filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+                filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+                filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+                mContext.registerReceiver(this, filter);
+                mRegistered = true;
+            } else if (!listening && mRegistered) {
+                if (DEBUG) Log.d(TAG, "Unregistering receiver");
+                mContext.unregisterReceiver(this);
+                mRegistered = false;
+            }
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Log.d(TAG, "onReceive " + intent.getAction());
+            if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
+                updateAccessPoints();
+                mScanning = false;
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 94fdd1f..4086022 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,8 @@
 import android.view.ViewTreeObserver;
 import android.view.animation.AnimationUtils;
 import android.widget.OverScroller;
+import android.widget.ScrollView;
+
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
@@ -150,7 +152,7 @@
     private float mMinTopOverScrollToEscape;
     private int mIntrinsicPadding;
     private int mNotificationTopPadding;
-    private int mMinStackHeight;
+    private float mTopPaddingOverflow;
     private boolean mDontReportNextOverScroll;
 
     /**
@@ -166,7 +168,10 @@
      * animating.
      */
     private boolean mOnlyScrollingInThisMotion;
-    private boolean mTouchEnabled = true;
+    private ViewGroup mScrollView;
+    private boolean mInterceptDelegateEnabled;
+    private boolean mDelegateToScrollView;
+
     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
@@ -258,7 +263,6 @@
                 R.dimen.min_top_overscroll_to_qs);
         mNotificationTopPadding = getResources().getDimensionPixelSize(
                 R.dimen.notifications_top_padding);
-        mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
     }
 
     private void updatePadding(boolean dimmed) {
@@ -421,7 +425,7 @@
         int minStackHeight = itemHeight + bottomStackPeekSize;
         int stackHeight;
         if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) {
-            setTranslationY(0);
+            setTranslationY(mTopPaddingOverflow);
             stackHeight = newStackHeight;
         } else {
 
@@ -466,6 +470,14 @@
         mLongClickListener = listener;
     }
 
+    public void setScrollView(ViewGroup scrollView) {
+        mScrollView = scrollView;
+    }
+
+    public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
+        mInterceptDelegateEnabled = interceptDelegateEnabled;
+    }
+
     public void onChildDismissed(View v) {
         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
         final View veto = v.findViewById(R.id.veto);
@@ -536,7 +548,7 @@
             }
             float childTop = slidingChild.getTranslationY();
             float top = childTop + slidingChild.getClipTopAmount();
-            float bottom = childTop + slidingChild.getActualHeight();
+            float bottom = top + slidingChild.getActualHeight();
             int left = slidingChild.getLeft();
             int right = slidingChild.getRight();
 
@@ -619,13 +631,17 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        if (!isEnabled()) {
-            return false;
-        }
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
+        if (mDelegateToScrollView) {
+            if (isCancelOrUp) {
+                mDelegateToScrollView = false;
+            }
+            transformTouchEvent(ev, this, mScrollView);
+            return mScrollView.onTouchEvent(ev);
+        }
         boolean expandWantsIt = false;
-        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
+        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
             if (isCancelOrUp) {
                 mExpandHelper.onlyObserveMovements(false);
             }
@@ -1195,7 +1211,7 @@
         return view.getHeight();
     }
 
-    private int getContentHeight() {
+    public int getContentHeight() {
         return mContentHeight;
     }
 
@@ -1271,17 +1287,32 @@
     public void updateTopPadding(float qsHeight, int scrollY, boolean animate) {
         float start = qsHeight - scrollY + mNotificationTopPadding;
         float stackHeight = getHeight() - start;
-        if (stackHeight <= mMinStackHeight) {
-            float overflow = mMinStackHeight - stackHeight;
-            stackHeight = mMinStackHeight;
+        int minStackHeight = getMinStackHeight();
+        if (stackHeight <= minStackHeight) {
+            float overflow = minStackHeight - stackHeight;
+            stackHeight = minStackHeight;
             start = getHeight() - stackHeight;
             setTranslationY(overflow);
+            mTopPaddingOverflow = overflow;
         } else {
             setTranslationY(0);
+            mTopPaddingOverflow = 0;
         }
         setTopPadding(clampPadding((int) start), animate);
     }
 
+    public int getNotificationTopPadding() {
+        return mNotificationTopPadding;
+    }
+
+    public int getMinStackHeight() {
+        return mCollapsedSize + mBottomStackPeekSize;
+    }
+
+    public float getTopPaddingOverflow() {
+        return mTopPaddingOverflow;
+    }
+
     public int getPeekHeight() {
         return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize;
     }
@@ -1327,11 +1358,25 @@
         }
     }
 
+    private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
+        ev.offsetLocation(sourceView.getX(), sourceView.getY());
+        ev.offsetLocation(-targetView.getX(), -targetView.getY());
+    }
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mInterceptDelegateEnabled) {
+            transformTouchEvent(ev, this, mScrollView);
+            if (mScrollView.onInterceptTouchEvent(ev)) {
+                mDelegateToScrollView = true;
+                removeLongPressCallback();
+                return true;
+            }
+            transformTouchEvent(ev, mScrollView, this);
+        }
         initDownStates(ev);
         boolean expandWantsIt = false;
-        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
+        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
         }
         boolean scrollWantsIt = false;
@@ -1854,6 +1899,10 @@
         mIntrinsicPadding = intrinsicPadding;
     }
 
+    public int getIntrinsicPadding() {
+        return mIntrinsicPadding;
+    }
+
     /**
      * @return the y position of the first notification
      */
@@ -1861,18 +1910,6 @@
         return mTopPadding + getTranslationY();
     }
 
-    public void setTouchEnabled(boolean touchEnabled) {
-        mTouchEnabled = touchEnabled;
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (!mTouchEnabled) {
-            return false;
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
     @Override
     public boolean shouldDelayChildPressedState() {
         return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index a8c25d8..7c522d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -113,7 +113,7 @@
         mBottomStackSlowDownLength = context.getResources()
                 .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
         mRoundedRectCornerRadius = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.notification_material_rounded_rect_radius);
+                R.dimen.notification_material_rounded_rect_radius);
     }
 
 
@@ -155,6 +155,17 @@
         updateDimmedActivated(ambientState, resultState, algorithmState);
         updateClipping(resultState, algorithmState);
         updateScrimAmount(resultState, algorithmState, ambientState.getScrimAmount());
+        updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
+    }
+
+    private void updateSpeedBumpState(StackScrollState resultState,
+            StackScrollAlgorithmState algorithmState, int speedBumpIndex) {
+        int childCount = algorithmState.visibleChildren.size();
+        for (int i = 0; i < childCount; i++) {
+            View child = algorithmState.visibleChildren.get(i);
+            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+            childViewState.belowSpeedBump = speedBumpIndex != -1 && i > speedBumpIndex;
+        }
     }
 
     private void updateScrimAmount(StackScrollState resultState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 9ae038a..f48739c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -151,6 +151,9 @@
                 // apply dark
                 child.setDark(state.dark, false /* animate */);
 
+                // apply speed bump state
+                child.setBelowSpeedBump(state.belowSpeedBump);
+
                 // apply scrimming
                 child.setScrimAmount(state.scrimAmount);
 
@@ -224,6 +227,7 @@
         float scale;
         boolean dimmed;
         boolean dark;
+        boolean belowSpeedBump;
 
         /**
          * A value between 0 and 1 indicating how much the view should be scrimmed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index f732cf0..0c84675 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -179,6 +179,9 @@
         // start dark animation
         child.setDark(viewState.dark, mAnimationFilter.animateDark);
 
+        // apply speed bump state
+        child.setBelowSpeedBump(viewState.belowSpeedBump);
+
         // apply scrimming
         child.setScrimAmount(viewState.scrimAmount);
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 99cba4d..53daaae 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -358,7 +358,7 @@
             // embedded mode
             mDialog = null;
             mView = LayoutInflater.from(mContext).inflate(
-                    com.android.systemui.R.layout.volume_panel, parent, true);
+                    com.android.systemui.R.layout.volume_panel, parent, false);
         }
         mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel);
         mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel);
@@ -384,6 +384,10 @@
         listenToRingerMode();
     }
 
+    public View getContentView() {
+        return mView;
+    }
+
     private void setLayoutDirection(int layoutDirection) {
         mPanel.setLayoutDirection(layoutDirection);
         updateStates();
@@ -628,7 +632,8 @@
         if (LOGD) Log.d(mTag, "expand mZenPanel=" + mZenPanel);
         if (mZenPanel == null) {
             mZenPanel = (ZenModePanel) mZenPanelStub.inflate();
-            mZenPanel.init(mZenController, mDialog != null ? 'D' : 'E');
+            final boolean isDialog = mDialog != null;
+            mZenPanel.init(mZenController, isDialog ? 'D' : 'E', isDialog);
             mZenPanel.setCallback(new ZenModePanel.Callback() {
                 @Override
                 public void onMoreSettings() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 798e7fa..9917944 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -67,6 +67,7 @@
     private char mLogTag = '?';
     private String mTag;
     private LinearLayout mConditions;
+    private View mMoreSettings;
     private Callback mCallback;
     private ZenModeController mController;
     private boolean mRequestingConditions;
@@ -91,7 +92,8 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mConditions = (LinearLayout) findViewById(android.R.id.content);
-        findViewById(android.R.id.button2).setOnClickListener(new View.OnClickListener() {
+        mMoreSettings = findViewById(android.R.id.button2);
+        mMoreSettings.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 fireMoreSettings();
@@ -154,12 +156,13 @@
         }
     }
 
-    public void init(ZenModeController controller, char logTag) {
+    public void init(ZenModeController controller, char logTag, boolean moreSettings) {
         mController = controller;
         mLogTag = logTag;
         updateTag();
         mExitConditionId = mController.getExitConditionId();
         if (DEBUG) Log.d(mTag, "init mExitConditionId=" + mExitConditionId);
+        mMoreSettings.setVisibility(moreSettings ? VISIBLE : GONE);
         mConditions.removeAllViews();
         mController.addCallback(mZenCallback);
         if (mShowing) {
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index cf28f05..3595f79 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -207,7 +207,7 @@
 
     protected final void pollDevices(DevicePollingCallback callback, int pickStrategy,
             int retryCount) {
-        mService.pollDevices(callback, pickStrategy, retryCount);
+        mService.pollDevices(callback, getSourceAddress(), pickStrategy, retryCount);
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 49f2d6f..7dfea6a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -200,7 +200,8 @@
             int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
             if (curAddress != HdmiCec.ADDR_UNREGISTERED
                     && deviceType == HdmiCec.getTypeFromAddress(curAddress)) {
-                if (!sendPollMessage(curAddress, RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) {
+                if (!sendPollMessage(curAddress, curAddress,
+                        RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) {
                     logicalAddress = curAddress;
                     break;
                 }
@@ -358,16 +359,18 @@
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      *
      * @param callback an interface used to get a list of all remote devices' address
+     * @param sourceAddress a logical address of source device where sends polling message
      * @param pickStrategy strategy how to pick polling candidates
      * @param retryCount the number of retry used to send polling message to remote devices
      */
     @ServiceThreadOnly
-    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
+    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
+            int retryCount) {
         assertRunOnServiceThread();
 
         // Extract polling candidates. No need to poll against local devices.
         List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
-        runDevicePolling(pollingCandidates, retryCount, callback);
+        runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback);
     }
 
     /**
@@ -428,7 +431,8 @@
     }
 
     @ServiceThreadOnly
-    private void runDevicePolling(final List<Integer> candidates, final int retryCount,
+    private void runDevicePolling(final int sourceAddress,
+            final List<Integer> candidates, final int retryCount,
             final DevicePollingCallback callback) {
         assertRunOnServiceThread();
         runOnIoThread(new Runnable() {
@@ -436,7 +440,7 @@
             public void run() {
                 final ArrayList<Integer> allocated = new ArrayList<>();
                 for (Integer address : candidates) {
-                    if (sendPollMessage(address, retryCount)) {
+                    if (sendPollMessage(sourceAddress, address, retryCount)) {
                         allocated.add(address);
                     }
                 }
@@ -453,15 +457,14 @@
     }
 
     @IoThreadOnly
-    private boolean sendPollMessage(int address, int retryCount) {
+    private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) {
         assertRunOnIoThread();
         for (int i = 0; i < retryCount; ++i) {
-            // <Polling Message> is a message which has empty body and
-            // uses same address for both source and destination address.
+            // <Polling Message> is a message which has empty body.
             // If sending <Polling Message> failed (NAK), it becomes
             // new logical address for the device because no device uses
             // it as logical address of the device.
-            if (nativeSendCecCommand(mNativePtr, address, address, EMPTY_BODY)
+            if (nativeSendCecCommand(mNativePtr, sourceAddress, destinationAddress, EMPTY_BODY)
                     == HdmiConstants.SEND_RESULT_SUCCESS) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
index 7080a56..c0c8424 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
@@ -175,15 +175,21 @@
         private final int mAndroidKeycode;
         private final int mCecKeycode;
         private final int mParam;
+        private final boolean mIsRepeatable;
 
-        private KeycodeEntry(int androidKeycode, int cecKeycode, int param) {
-            this.mAndroidKeycode = androidKeycode;
-            this.mCecKeycode = cecKeycode;
-            this.mParam = param;
+        private KeycodeEntry(int androidKeycode, int cecKeycode, int param, boolean isRepeatable) {
+            mAndroidKeycode = androidKeycode;
+            mCecKeycode = cecKeycode;
+            mParam = param;
+            mIsRepeatable = isRepeatable;
         }
 
         private KeycodeEntry(int androidKeycode, int cecKeycode) {
-            this(androidKeycode, cecKeycode, NO_PARAM);
+            this(androidKeycode, cecKeycode, NO_PARAM, true);
+        }
+
+        private KeycodeEntry(int androidKeycode, int cecKeycode, boolean isRepeatable) {
+            this(androidKeycode, cecKeycode, NO_PARAM, isRepeatable);
         }
 
         private byte[] toCecKeycodeIfMatched(int androidKeycode) {
@@ -210,6 +216,14 @@
                 return UNSUPPORTED_KEYCODE;
             }
         }
+
+        private Boolean isRepeatableIfMatched(int androidKeycode) {
+            if (mAndroidKeycode == androidKeycode) {
+                return mIsRepeatable;
+            } else {
+                return null;
+            }
+        }
     }
 
     // Keycode entry container for all mappings.
@@ -221,19 +235,26 @@
             new KeycodeEntry(KeyEvent.KEYCODE_DPAD_LEFT, CEC_KEYCODE_LEFT),
             new KeycodeEntry(KeyEvent.KEYCODE_DPAD_RIGHT, CEC_KEYCODE_RIGHT),
             // No Android keycode defined for CEC_KEYCODE_RIGHT_UP
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RIGHT_UP),
             // No Android keycode defined for CEC_KEYCODE_RIGHT_DOWN
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RIGHT_DOWN),
             // No Android keycode defined for CEC_KEYCODE_LEFT_UP
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_UP),
             // No Android keycode defined for CEC_KEYCODE_LEFT_DOWN
-            new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU),
-            new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU),
-            new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_CONTENTS_MENU),
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_DOWN),
+            new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU, false),
+            new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU, false),
+            new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_CONTENTS_MENU, false),
             // No Android keycode defined for CEC_KEYCODE_FAVORITE_MENU
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_FAVORITE_MENU),
             new KeycodeEntry(KeyEvent.KEYCODE_BACK, CEC_KEYCODE_EXIT),
             // RESERVED
             new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_TOP_MENU, CEC_KEYCODE_MEDIA_TOP_MENU),
             // No Android keycode defined for CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU),
             // RESERVED
             // No Android keycode defined for CEC_KEYCODE_NUMBER_ENTRY_MODE
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_NUMBER_ENTRY_MODE),
             new KeycodeEntry(KeyEvent.KEYCODE_11, CEC_KEYCODE_NUMBER_11),
             new KeycodeEntry(KeyEvent.KEYCODE_12, CEC_KEYCODE_NUMBER_12),
             new KeycodeEntry(KeyEvent.KEYCODE_0, CEC_KEYCODE_NUMBER_0_OR_NUMBER_10),
@@ -251,20 +272,23 @@
             new KeycodeEntry(KeyEvent.KEYCODE_CLEAR, CEC_KEYCODE_CLEAR),
             // RESERVED
             // No Android keycode defined for CEC_KEYCODE_NEXT_FAVORITE
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_NEXT_FAVORITE),
             new KeycodeEntry(KeyEvent.KEYCODE_CHANNEL_UP, CEC_KEYCODE_CHANNEL_UP),
             new KeycodeEntry(KeyEvent.KEYCODE_CHANNEL_DOWN, CEC_KEYCODE_CHANNEL_DOWN),
             new KeycodeEntry(KeyEvent.KEYCODE_LAST_CHANNEL, CEC_KEYCODE_PREVIOUS_CHANNEL),
             // No Android keycode defined for CEC_KEYCODE_SOUND_SELECT
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SOUND_SELECT),
             new KeycodeEntry(KeyEvent.KEYCODE_TV_INPUT, CEC_KEYCODE_INPUT_SELECT),
             new KeycodeEntry(KeyEvent.KEYCODE_INFO, CEC_KEYCODE_DISPLAY_INFORMATION),
             // No Android keycode defined for CEC_KEYCODE_HELP
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_HELP),
             new KeycodeEntry(KeyEvent.KEYCODE_PAGE_UP, CEC_KEYCODE_PAGE_UP),
             new KeycodeEntry(KeyEvent.KEYCODE_PAGE_DOWN, CEC_KEYCODE_PAGE_DOWN),
             // RESERVED
-            new KeycodeEntry(KeyEvent.KEYCODE_POWER, CEC_KEYCODE_POWER),
+            new KeycodeEntry(KeyEvent.KEYCODE_POWER, CEC_KEYCODE_POWER, false),
             new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_UP, CEC_KEYCODE_VOLUME_UP),
             new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_DOWN, CEC_KEYCODE_VOLUME_DOWN),
-            new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_MUTE, CEC_KEYCODE_MUTE),
+            new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_MUTE, CEC_KEYCODE_MUTE, false),
             new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PLAY, CEC_KEYCODE_PLAY),
             new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_STOP, CEC_KEYCODE_STOP),
             new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PAUSE, CEC_KEYCODE_PAUSE),
@@ -275,33 +299,57 @@
             new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_NEXT, CEC_KEYCODE_FORWARD),
             new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PREVIOUS, CEC_KEYCODE_BACKWARD),
             // No Android keycode defined for CEC_KEYCODE_STOP_RECORD
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_STOP_RECORD),
             // No Android keycode defined for CEC_KEYCODE_PAUSE_RECORD
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_RECORD),
             // No Android keycode defined for CEC_KEYCODE_RESERVED
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RESERVED),
             // No Android keycode defined for CEC_KEYCODE_ANGLE
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_ANGLE),
             // No Android keycode defined for CEC_KEYCODE_SUB_PICTURE
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SUB_PICTURE),
             // No Android keycode defined for CEC_KEYCODE_VIDEO_ON_DEMAND
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_VIDEO_ON_DEMAND),
             new KeycodeEntry(KeyEvent.KEYCODE_GUIDE, CEC_KEYCODE_ELECTRONIC_PROGRAM_GUIDE),
             // No Android keycode defined for CEC_KEYCODE_TIMER_PROGRAMMING
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_TIMER_PROGRAMMING),
             // No Android keycode defined for CEC_KEYCODE_INITIAL_CONFIGURATION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_INITIAL_CONFIGURATION),
             // No Android keycode defined for CEC_KEYCODE_SELECT_BROADCAST_TYPE
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_BROADCAST_TYPE),
             // No Android keycode defined for CEC_KEYCODE_SELECT_SOUND_PRESENTATION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_SOUND_PRESENTATION),
             // RESERVED
             // The following deterministic key definitions do not need key mapping
             // since they are supposed to be generated programmatically only.
             // No Android keycode defined for CEC_KEYCODE_PLAY_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PLAY_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_PAUSE_PLAY_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_PLAY_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_RECORD_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RECORD_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_PAUSE_RECORD_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_RECORD_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_STOP_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_STOP_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_MUTE_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_MUTE_FUNCTION, false),
             // No Android keycode defined for CEC_KEYCODE_RESTORE_VOLUME_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RESTORE_VOLUME_FUNCTION, false),
             // No Android keycode defined for CEC_KEYCODE_TUNE_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_TUNE_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_SELECT_MEDIA_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_MEDIA_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_POWER_TOGGLE_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_TOGGLE_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_POWER_OFF_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_OFF_FUNCTION),
             // No Android keycode defined for CEC_KEYCODE_POWER_ON_FUNCTION
+            new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_ON_FUNCTION, false),
             // RESERVED
             new KeycodeEntry(KeyEvent.KEYCODE_PROG_BLUE, CEC_KEYCODE_F1_BLUE),
             new KeycodeEntry(KeyEvent.KEYCODE_PROG_RED, CEC_KEYCODE_F2_RED),
@@ -346,4 +394,20 @@
         }
         return UNSUPPORTED_KEYCODE;
     }
+
+    /**
+     * Whether the given {@code androidKeycode} is repeatable key or not.
+     *
+     * @param androidKeycode keycode of android
+     * @return false if the given {@code androidKeycode} is not supported key code
+     */
+    static boolean isRepeatableKey(int androidKeycode) {
+        for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) {
+            Boolean isRepeatable = KEYCODE_ENTRIES[i].isRepeatableIfMatched(androidKeycode);
+            if (isRepeatable != null) {
+                return isRepeatable;
+            }
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9ef818d..95d8333 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -491,14 +491,17 @@
      * devices.
      *
      * @param callback an interface used to get a list of all remote devices' address
+     * @param sourceAddress a logical address of source device where sends polling message
      * @param pickStrategy strategy how to pick polling candidates
      * @param retryCount the number of retry used to send polling message to remote devices
      * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
      */
     @ServiceThreadOnly
-    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
+    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
+            int retryCount) {
         assertRunOnServiceThread();
-        mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount);
+        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
+                retryCount);
     }
 
     private int checkPollStrategy(int pickStrategy) {
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 5d81251..a525cda 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -44,24 +44,30 @@
     private final int mTargetAddress;
 
     // The key code of the last key press event the action is passed via processKeyEvent.
-    private int mLastKeyCode;
+    private int mLastKeycode;
 
     /**
      * Constructor.
      *
      * @param source {@link HdmiCecLocalDevice} instance
      * @param targetAddress logical address of the device to send the keys to
-     * @param keyCode remote control key code as defined in {@link KeyEvent}
+     * @param keycode remote control key code as defined in {@link KeyEvent}
      */
-    SendKeyAction(HdmiCecLocalDevice source, int targetAddress, int keyCode) {
+    SendKeyAction(HdmiCecLocalDevice source, int targetAddress, int keycode) {
         super(source);
         mTargetAddress = targetAddress;
-        mLastKeyCode = keyCode;
+        mLastKeycode = keycode;
     }
 
     @Override
     public boolean start() {
-        sendKeyDown(mLastKeyCode);
+        sendKeyDown(mLastKeycode);
+        // finish action for non-repeatable key.
+        if (!HdmiCecKeycode.isRepeatableKey(mLastKeycode)) {
+            sendKeyUp();
+            finish();
+            return true;
+        }
         mState = STATE_PROCESSING_KEYCODE;
         addTimer(mState, IRT_MS);
         return true;
@@ -70,10 +76,10 @@
     /**
      * Called when a key event should be handled for the action.
      *
-     * @param keyCode key code of {@link KeyEvent} object
+     * @param keycode key code of {@link KeyEvent} object
      * @param isPressed true if the key event is of {@link KeyEvent#ACTION_DOWN}
      */
-    void processKeyEvent(int keyCode, boolean isPressed) {
+    void processKeyEvent(int keycode, boolean isPressed) {
         if (mState != STATE_PROCESSING_KEYCODE) {
             Slog.w(TAG, "Not in a valid state");
             return;
@@ -84,27 +90,32 @@
         // Key release event indicates that the action shall be finished. Send UCR
         // command and terminate the action. Other release events are ignored.
         if (isPressed) {
-            if (keyCode != mLastKeyCode) {
+            if (keycode != mLastKeycode) {
+                if (!HdmiCecKeycode.isRepeatableKey(keycode)) {
+                    sendKeyUp();
+                    finish();
+                    return;
+                }
                 mActionTimer.clearTimerMessage();
-                sendKeyDown(keyCode);
+                sendKeyDown(keycode);
                 addTimer(mState, IRT_MS);
-                mLastKeyCode = keyCode;
+                mLastKeycode = keycode;
             }
         } else {
-            if (keyCode == mLastKeyCode) {
+            if (keycode == mLastKeycode) {
                 sendKeyUp();
                 finish();
             }
         }
     }
 
-    private void sendKeyDown(int keyCode) {
-        byte[] keyCodeAndParam = getCecKeyCodeAndParam(keyCode);
-        if (keyCodeAndParam == null) {
+    private void sendKeyDown(int keycode) {
+        byte[] keycodeAndParam = getCecKeycodeAndParam(keycode);
+        if (keycodeAndParam == null) {
             return;
         }
         sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
-                mTargetAddress, keyCodeAndParam));
+                mTargetAddress, keycodeAndParam));
     }
 
     private void sendKeyUp() {
@@ -128,7 +139,7 @@
             Slog.w(TAG, "Not in a valid state");
             return;
         }
-        sendKeyDown(mLastKeyCode);
+        sendKeyDown(mLastKeycode);
         addTimer(mState, IRT_MS);
     }
 
@@ -137,7 +148,7 @@
     // Broadcast' with the parameter 'cable', for instance, shall have its counterpart such as
     // KeyEvent.KEYCODE_TV_BROADCAST_CABLE.
     // The return byte array contains both UI command (keycode) and optional parameter.
-    private byte[] getCecKeyCodeAndParam(int keyCode) {
-        return HdmiCecKeycode.androidKeyToCecKey(keyCode);
+    private byte[] getCecKeycodeAndParam(int keycode) {
+        return HdmiCecKeycode.androidKeyToCecKey(keycode);
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bc14888..c8496e4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1551,8 +1551,16 @@
                         mNotificationsByKey.remove(old.sbn.getKey());
                         r.isUpdate = true;
                     }
+
                     mNotificationsByKey.put(n.getKey(), r);
 
+                    // Ensure if this is a foreground service that the proper additional
+                    // flags are set.
+                    if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                        notification.flags |= Notification.FLAG_ONGOING_EVENT
+                                | Notification.FLAG_NO_CLEAR;
+                    }
+
                     applyZenModeLocked(r);
 
                     Collections.sort(mNotificationList, mRankingComparator);
@@ -1571,13 +1579,6 @@
                                 + n.getPackageName());
                     }
 
-                    // Ensure if this is a foreground service that the proper additional
-                    // flags are set.
-                    if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                        notification.flags |= Notification.FLAG_ONGOING_EVENT
-                                | Notification.FLAG_NO_CLEAR;
-                    }
-
                     buzzBeepBlinkLocked(r);
                 }
             }
@@ -1872,10 +1873,9 @@
         }
     }
 
-    // let zen mode evaluate this record and then make note of that for the future
+    // let zen mode evaluate this record
     private void applyZenModeLocked(NotificationRecord record) {
-        record.setIntercepted(mZenModeHelper.shouldIntercept(record, record.wasTouchedByZen()));
-        record.setTouchedByZen();
+        record.setIntercepted(mZenModeHelper.shouldIntercept(record));
     }
 
     // lock on mNotificationList
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 13fb986..6b60ea4 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -51,8 +51,6 @@
 
     // is this notification currently being intercepted by Zen Mode?
     private boolean mIntercept;
-    // InterceptedNotifications needs to know if this has been previously evaluated.
-    private boolean mTouchedByZen;
 
     // The timestamp used for ranking.
     private long mRankingTimeMs;
@@ -71,7 +69,6 @@
     public void copyRankingInformation(NotificationRecord previous) {
         mContactAffinity = previous.mContactAffinity;
         mRecentlyIntrusive = previous.mRecentlyIntrusive;
-        mTouchedByZen = previous.mTouchedByZen;
         mIntercept = previous.mIntercept;
         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
     }
@@ -204,14 +201,6 @@
         return mIntercept;
     }
 
-    public boolean wasTouchedByZen() {
-        return mTouchedByZen;
-    }
-
-    public void setTouchedByZen() {
-        mTouchedByZen = true;
-    }
-
     /**
      * Returns the timestamp to use for time-based sorting in the ranker.
      */
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b95db9c..7bac3dc 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -129,12 +129,8 @@
         mCallbacks.add(callback);
     }
 
-    public boolean shouldIntercept(NotificationRecord record, boolean previouslySeen) {
+    public boolean shouldIntercept(NotificationRecord record) {
         if (mZenMode != Global.ZEN_MODE_OFF) {
-            if (previouslySeen && !record.isIntercepted()) {
-                // notifications never transition from not intercepted to intercepted
-                return false;
-            }
             if (isSystem(record)) {
                 return false;
             }
diff --git a/telephony/java/com/android/internal/telephony/IMms.aidl b/telephony/java/com/android/internal/telephony/IMms.aidl
index a745420..4da90a5 100644
--- a/telephony/java/com/android/internal/telephony/IMms.aidl
+++ b/telephony/java/com/android/internal/telephony/IMms.aidl
@@ -44,4 +44,25 @@
      *  broadcast when the message is downloaded, or the download is failed
      */
     void downloadMessage(String callingPkg, String locationUrl, in PendingIntent downloadedIntent);
+
+    /**
+     * Update the status of a pending (send-by-IP) MMS message handled by the carrier app.
+     * If the carrier app fails to send this message, it would be resent via carrier network.
+     *
+     * @param messageRef the reference number of the MMS message.
+     * @param success True if and only if the message was sent successfully. If its value is
+     *  false, this message should be resent via carrier network
+     */
+    void updateMmsSendStatus(int messageRef, boolean success);
+
+    /**
+     * Update the status of a pending (download-by-IP) MMS message handled by the carrier app.
+     * If the carrier app fails to download this message, it would be re-downloaded via carrier
+     * network.
+     *
+     * @param messageRef the reference number of the MMS message.
+     * @param pdu non-empty if downloaded successfully, otherwise, it is empty and the message
+     *  will be downloaded via carrier network
+     */
+    void updateMmsDownloadStatus(int messageRef, in byte[] pdu);
 }