Merge "Import translations. DO NOT MERGE" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index b9f932b..1d891a6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5225,6 +5225,7 @@
 
   public class NotificationManager {
     method public android.app.AutomaticZenRule addAutomaticZenRule(android.app.AutomaticZenRule);
+    method public boolean areNotificationsEnabled();
     method public void cancel(int);
     method public void cancel(java.lang.String, int);
     method public void cancelAll();
@@ -5232,6 +5233,7 @@
     method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
     method public java.util.List<android.app.AutomaticZenRule> getAutomaticZenRules();
     method public final int getCurrentInterruptionFilter();
+    method public int getImportance(java.lang.String);
     method public android.app.NotificationManager.Policy getNotificationPolicy();
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
diff --git a/api/system-current.txt b/api/system-current.txt
index b404bb0..1ab5837 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5357,6 +5357,7 @@
 
   public class NotificationManager {
     method public android.app.AutomaticZenRule addAutomaticZenRule(android.app.AutomaticZenRule);
+    method public boolean areNotificationsEnabled();
     method public void cancel(int);
     method public void cancel(java.lang.String, int);
     method public void cancelAll();
@@ -5364,6 +5365,7 @@
     method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
     method public java.util.List<android.app.AutomaticZenRule> getAutomaticZenRules();
     method public final int getCurrentInterruptionFilter();
+    method public int getImportance(java.lang.String);
     method public android.app.NotificationManager.Policy getNotificationPolicy();
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
diff --git a/api/test-current.txt b/api/test-current.txt
index 9f8b8a8..49b8ffe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5225,6 +5225,7 @@
 
   public class NotificationManager {
     method public android.app.AutomaticZenRule addAutomaticZenRule(android.app.AutomaticZenRule);
+    method public boolean areNotificationsEnabled();
     method public void cancel(int);
     method public void cancel(java.lang.String, int);
     method public void cancelAll();
@@ -5232,6 +5233,7 @@
     method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
     method public java.util.List<android.app.AutomaticZenRule> getAutomaticZenRules();
     method public final int getCurrentInterruptionFilter();
+    method public int getImportance(java.lang.String);
     method public android.app.NotificationManager.Policy getNotificationPolicy();
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 40e58af..0d35cf0 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -46,6 +46,7 @@
 
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
+    boolean areNotificationsEnabled(String pkg);
 
     ParceledListSlice getTopics(String pkg, int uid);
     void setVisibilityOverride(String pkg, int uid, in Notification.Topic topic, int visibility);
@@ -54,6 +55,7 @@
     int getPriority(String pkg, int uid, in Notification.Topic topic);
     void setImportance(String pkg, int uid, in Notification.Topic topic, int importance);
     int getImportance(String pkg, int uid, in Notification.Topic topic);
+    int getTopicImportance(String pkg, String topicId);
     boolean doesAppUseTopics(String pkg, int uid);
     boolean hasBannedTopics(String pkg, int uid);
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index a83225d9..1f17024 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import com.android.internal.util.Preconditions;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -37,6 +39,7 @@
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.service.notification.IConditionListener;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
 import android.util.ArraySet;
@@ -503,6 +506,25 @@
         return false;
     }
 
+    public int getImportance(String topicId) {
+        Preconditions.checkNotNull(topicId);
+        INotificationManager service = getService();
+        try {
+            return service.getTopicImportance(mContext.getPackageName(), topicId);
+        } catch (RemoteException e) {
+        }
+        return NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+    }
+
+    public boolean areNotificationsEnabled() {
+        INotificationManager service = getService();
+        try {
+            return service.areNotificationsEnabled(mContext.getPackageName());
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
     /**
      * Checks the ability to read/modify notification policy for the calling package.
      *
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index 605f067..9ab62cc 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.os.UserHandle;
 import android.test.InstrumentationTestCase;
 import android.util.Pair;
 
@@ -116,14 +117,14 @@
             }
         }
 
-        Mockito.when(mMockPm.queryBroadcastReceivers(
+        Mockito.when(mMockPm.queryBroadcastReceiversAsUser(
                 Mockito.argThat(new ArgumentMatcher<Intent>() {
                     @Override
                     public boolean matches(Object object) {
                         Intent intent = (Intent) object;
                         return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction());
                     }
-                }), Mockito.eq(0)))
+                }), Mockito.eq(0), Mockito.eq(UserHandle.USER_SYSTEM)))
                 .thenReturn(receivers);
     }
 
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_subdirectory_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_subdirectory_arrow_am_alpha.png
deleted file mode 100644
index e4881dd..0000000
--- a/packages/DocumentsUI/res/drawable-hdpi/ic_subdirectory_arrow_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_subdirectory_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_subdirectory_arrow_am_alpha.png
deleted file mode 100644
index 66725e5..0000000
--- a/packages/DocumentsUI/res/drawable-mdpi/ic_subdirectory_arrow_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_subdirectory_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_subdirectory_arrow_am_alpha.png
deleted file mode 100644
index cdf21b1..0000000
--- a/packages/DocumentsUI/res/drawable-xhdpi/ic_subdirectory_arrow_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_subdirectory_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_subdirectory_arrow_am_alpha.png
deleted file mode 100644
index 050313f..0000000
--- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_subdirectory_arrow_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_subdirectory_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_subdirectory_arrow_am_alpha.png
deleted file mode 100644
index 8fddb85..0000000
--- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_subdirectory_arrow_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml b/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml
index 5723233..0f34ba4 100644
--- a/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml
+++ b/packages/DocumentsUI/res/drawable/ic_subdirectory_arrow.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_subdirectory_arrow_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="?android:attr/colorControlNormal"
+        android:pathData="M19 15l-6 6,-1.42,-1.42L15.17 16H4V4h2v10h9.17l-3.59,-3.58L13 9l6 6z"/>
+</vector>
diff --git a/packages/DocumentsUI/res/layout/item_subdir.xml b/packages/DocumentsUI/res/layout/item_subdir.xml
index b2a739a..821432d 100644
--- a/packages/DocumentsUI/res/layout/item_subdir.xml
+++ b/packages/DocumentsUI/res/layout/item_subdir.xml
@@ -26,8 +26,8 @@
 
     <ImageView
         android:id="@+id/subdir"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
         android:paddingEnd="8dp"
         android:scaleType="centerInside"
         android:visibility="gone"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 7fe881e..174984c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -151,6 +151,7 @@
     private MultiSelectManager mSelectionManager;
     private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
     private ItemEventListener mItemEventListener = new ItemEventListener();
+    private FocusManager mFocusManager;
 
     private IconHelper mIconHelper;
 
@@ -262,6 +263,8 @@
 
         mSelectionManager.addCallback(selectionListener);
 
+        mFocusManager = new FocusManager(mRecView, mSelectionManager);
+
         mModel = new Model();
         mModel.addUpdateListener(mAdapter);
         mModel.addUpdateListener(mModelUpdateListener);
@@ -1249,165 +1252,17 @@
                 return false;
             }
 
-            boolean handled = false;
-            if (Events.isNavigationKeyCode(keyCode)) {
-                // Find the target item and focus it.
-                int endPos = findTargetPosition(doc.itemView, keyCode);
-
-                if (endPos != RecyclerView.NO_POSITION) {
-                    focusItem(endPos);
-
-                    // Handle any necessary adjustments to selection.
-                    boolean extendSelection = event.isShiftPressed();
-                    if (extendSelection) {
-                        int startPos = doc.getAdapterPosition();
-                        mSelectionManager.selectRange(startPos, endPos);
-                    }
-                    handled = true;
-                }
-            } else {
-                // Handle enter key events
-                if (keyCode == KeyEvent.KEYCODE_ENTER) {
-                    handled = onActivate(doc);
-                }
+            if (mFocusManager.handleKey(doc, keyCode, event)) {
+                return true;
             }
 
-            return handled;
+            // Handle enter key events
+            if (keyCode == KeyEvent.KEYCODE_ENTER) {
+                return onActivate(doc);
+            }
+
+            return false;
         }
-
-        /**
-         * Finds the destination position where the focus should land for a given navigation event.
-         *
-         * @param view The view that received the event.
-         * @param keyCode The key code for the event.
-         * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION.
-         */
-        private int findTargetPosition(View view, int keyCode) {
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_MOVE_HOME:
-                    return 0;
-                case KeyEvent.KEYCODE_MOVE_END:
-                    return mAdapter.getItemCount() - 1;
-                case KeyEvent.KEYCODE_PAGE_UP:
-                case KeyEvent.KEYCODE_PAGE_DOWN:
-                    return findTargetPositionByPage(view, keyCode);
-            }
-
-            // Find a navigation target based on the arrow key that the user pressed.
-            int searchDir = -1;
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_DPAD_UP:
-                    searchDir = View.FOCUS_UP;
-                    break;
-                case KeyEvent.KEYCODE_DPAD_DOWN:
-                    searchDir = View.FOCUS_DOWN;
-                    break;
-                case KeyEvent.KEYCODE_DPAD_LEFT:
-                    searchDir = View.FOCUS_LEFT;
-                    break;
-                case KeyEvent.KEYCODE_DPAD_RIGHT:
-                    searchDir = View.FOCUS_RIGHT;
-                    break;
-            }
-
-            if (searchDir != -1) {
-                View targetView = view.focusSearch(searchDir);
-                // TargetView can be null, for example, if the user pressed <down> at the bottom
-                // of the list.
-                if (targetView != null) {
-                    // Ignore navigation targets that aren't items in the RecyclerView.
-                    if (targetView.getParent() == mRecView) {
-                        return mRecView.getChildAdapterPosition(targetView);
-                    }
-                }
-            }
-
-            return RecyclerView.NO_POSITION;
-        }
-
-        /**
-         * Given a PgUp/PgDn event and the current view, find the position of the target view.
-         * This returns:
-         * <li>The position of the topmost (or bottom-most) visible item, if the current item is not
-         *     the top- or bottom-most visible item.
-         * <li>The position of an item that is one page's worth of items up (or down) if the current
-         *      item is the top- or bottom-most visible item.
-         * <li>The first (or last) item, if paging up (or down) would go past those limits.
-         * @param view The view that received the key event.
-         * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN.
-         * @return The adapter position of the target item.
-         */
-        private int findTargetPositionByPage(View view, int keyCode) {
-            int first = mLayout.findFirstVisibleItemPosition();
-            int last = mLayout.findLastVisibleItemPosition();
-            int current = mRecView.getChildAdapterPosition(view);
-            int pageSize = last - first + 1;
-
-            if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
-                if (current > first) {
-                    // If the current item isn't the first item, target the first item.
-                    return first;
-                } else {
-                    // If the current item is the first item, target the item one page up.
-                    int target = current - pageSize;
-                    return target < 0 ? 0 : target;
-                }
-            }
-
-            if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
-                if (current < last) {
-                    // If the current item isn't the last item, target the last item.
-                    return last;
-                } else {
-                    // If the current item is the last item, target the item one page down.
-                    int target = current + pageSize;
-                    int max = mAdapter.getItemCount() - 1;
-                    return target < max ? target : max;
-                }
-            }
-
-            throw new IllegalArgumentException("Unsupported keyCode: " + keyCode);
-        }
-
-        /**
-         * Requests focus for the item in the given adapter position, scrolling the RecyclerView if
-         * necessary.
-         *
-         * @param pos
-         */
-        public void focusItem(final int pos) {
-            // If the item is already in view, focus it; otherwise, scroll to it and focus it.
-            RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos);
-            if (vh != null) {
-                vh.itemView.requestFocus();
-            } else {
-                mRecView.smoothScrollToPosition(pos);
-                // Set a one-time listener to request focus when the scroll has completed.
-                mRecView.addOnScrollListener(
-                    new RecyclerView.OnScrollListener() {
-                        @Override
-                        public void onScrollStateChanged (RecyclerView view, int newState) {
-                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
-                                // When scrolling stops, find the item and focus it.
-                                RecyclerView.ViewHolder vh =
-                                        view.findViewHolderForAdapterPosition(pos);
-                                if (vh != null) {
-                                    vh.itemView.requestFocus();
-                                } else {
-                                    // This might happen in weird corner cases, e.g. if the user is
-                                    // scrolling while a delete operation is in progress.  In that
-                                    // case, just don't attempt to focus the missing item.
-                                    Log.w(
-                                        TAG, "Unable to focus position " + pos + " after a scroll");
-                                }
-                                view.removeOnScrollListener(this);
-                            }
-                        }
-                    });
-            }
-        }
-
-
     }
 
     private final class ModelUpdateListener implements Model.UpdateListener {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
new file mode 100644
index 0000000..86b9146
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.android.documentsui.Events;
+
+/**
+ * A class that handles navigation and focus within the DirectoryFragment.
+ */
+class FocusManager {
+    private static final String TAG = "FocusManager";
+
+    private RecyclerView mView;
+    private RecyclerView.Adapter<?> mAdapter;
+    private LinearLayoutManager mLayout;
+    private MultiSelectManager mSelectionManager;
+
+    public FocusManager(RecyclerView view, MultiSelectManager selectionManager) {
+        mView = view;
+        mAdapter = view.getAdapter();
+        mLayout = (LinearLayoutManager) view.getLayoutManager();
+        mSelectionManager = selectionManager;
+    }
+
+    /**
+     * Handles navigation (setting focus, adjusting selection if needed) arising from incoming key
+     * events.
+     *
+     * @param doc The DocumentHolder receiving the key event.
+     * @param keyCode
+     * @param event
+     * @return Whether the event was handled.
+     */
+    public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+        boolean handled = false;
+        if (Events.isNavigationKeyCode(keyCode)) {
+            // Find the target item and focus it.
+            int endPos = findTargetPosition(doc.itemView, keyCode, event);
+
+            if (endPos != RecyclerView.NO_POSITION) {
+                focusItem(endPos);
+
+                // Handle any necessary adjustments to selection.
+                boolean extendSelection = event.isShiftPressed();
+                if (extendSelection) {
+                    int startPos = doc.getAdapterPosition();
+                    mSelectionManager.selectRange(startPos, endPos);
+                }
+                handled = true;
+            }
+        }
+        return handled;
+    }
+
+    /**
+     * Finds the destination position where the focus should land for a given navigation event.
+     *
+     * @param view The view that received the event.
+     * @param keyCode The key code for the event.
+     * @param event
+     * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION.
+     */
+    private int findTargetPosition(View view, int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MOVE_HOME:
+                return 0;
+            case KeyEvent.KEYCODE_MOVE_END:
+                return mAdapter.getItemCount() - 1;
+            case KeyEvent.KEYCODE_PAGE_UP:
+            case KeyEvent.KEYCODE_PAGE_DOWN:
+                return findPagedTargetPosition(view, keyCode, event);
+        }
+
+        // Find a navigation target based on the arrow key that the user pressed.
+        int searchDir = -1;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_UP:
+                searchDir = View.FOCUS_UP;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                searchDir = View.FOCUS_DOWN;
+                break;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                searchDir = View.FOCUS_LEFT;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                searchDir = View.FOCUS_RIGHT;
+                break;
+        }
+
+        if (searchDir != -1) {
+            View targetView = view.focusSearch(searchDir);
+            // TargetView can be null, for example, if the user pressed <down> at the bottom
+            // of the list.
+            if (targetView != null) {
+                // Ignore navigation targets that aren't items in the RecyclerView.
+                if (targetView.getParent() == mView) {
+                    return mView.getChildAdapterPosition(targetView);
+                }
+            }
+        }
+
+        return RecyclerView.NO_POSITION;
+    }
+
+    /**
+     * Given a PgUp/PgDn event and the current view, find the position of the target view.
+     * This returns:
+     * <li>The position of the topmost (or bottom-most) visible item, if the current item is not
+     *     the top- or bottom-most visible item.
+     * <li>The position of an item that is one page's worth of items up (or down) if the current
+     *      item is the top- or bottom-most visible item.
+     * <li>The first (or last) item, if paging up (or down) would go past those limits.
+     * @param view The view that received the key event.
+     * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN.
+     * @param event
+     * @return The adapter position of the target item.
+     */
+    private int findPagedTargetPosition(View view, int keyCode, KeyEvent event) {
+        int first = mLayout.findFirstVisibleItemPosition();
+        int last = mLayout.findLastVisibleItemPosition();
+        int current = mView.getChildAdapterPosition(view);
+        int pageSize = last - first + 1;
+
+        if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
+            if (current > first) {
+                // If the current item isn't the first item, target the first item.
+                return first;
+            } else {
+                // If the current item is the first item, target the item one page up.
+                int target = current - pageSize;
+                return target < 0 ? 0 : target;
+            }
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
+            if (current < last) {
+                // If the current item isn't the last item, target the last item.
+                return last;
+            } else {
+                // If the current item is the last item, target the item one page down.
+                int target = current + pageSize;
+                int max = mAdapter.getItemCount() - 1;
+                return target < max ? target : max;
+            }
+        }
+
+        throw new IllegalArgumentException("Unsupported keyCode: " + keyCode);
+    }
+
+    /**
+     * Requests focus for the item in the given adapter position, scrolling the RecyclerView if
+     * necessary.
+     *
+     * @param pos
+     */
+    private void focusItem(final int pos) {
+        // If the item is already in view, focus it; otherwise, scroll to it and focus it.
+        RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos);
+        if (vh != null) {
+            vh.itemView.requestFocus();
+        } else {
+            mView.smoothScrollToPosition(pos);
+            // Set a one-time listener to request focus when the scroll has completed.
+            mView.addOnScrollListener(
+                    new RecyclerView.OnScrollListener() {
+                        @Override
+                        public void onScrollStateChanged(RecyclerView view, int newState) {
+                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                                // When scrolling stops, find the item and focus it.
+                                RecyclerView.ViewHolder vh =
+                                        view.findViewHolderForAdapterPosition(pos);
+                                if (vh != null) {
+                                    vh.itemView.requestFocus();
+                                } else {
+                                    // This might happen in weird corner cases, e.g. if the user is
+                                    // scrolling while a delete operation is in progress. In that
+                                    // case, just don't attempt to focus the missing item.
+                                    Log.w(TAG, "Unable to focus position " + pos + " after scroll");
+                                }
+                                view.removeOnScrollListener(this);
+                            }
+                        }
+                    });
+        }
+    }
+}
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index bb37b83..28b9a5e6 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -19,12 +19,15 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@drawable/qs_background_primary"
-        android:paddingBottom="8dp"
-        android:elevation="2dp">
+        android:paddingBottom="8dp">
 
     <com.android.systemui.qs.QSPanel
             android:id="@+id/quick_settings_panel"
             android:background="#0000"
+            android:layout_marginTop="@dimen/status_bar_header_height"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
+
+    <include layout="@layout/quick_status_bar_expanded_header" />
+
 </com.android.systemui.qs.QSContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 89abe2d..0ebbdd8 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,6 +31,12 @@
         android:layout_height="wrap_content"
         android:visibility="gone" />
 
+    <include
+        layout="@layout/qs_panel"
+        android:layout_width="@dimen/notification_panel_width"
+        android:layout_height="wrap_content"
+        android:layout_gravity="@integer/notification_panel_layout_gravity" />
+
     <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -51,11 +57,6 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="vertical">
-                <include
-                    layout="@layout/qs_panel"
-                    android:layout_marginTop="@dimen/status_bar_header_height_expanded"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
 
                 <!-- A view to reserve space for the collapsed stack -->
                 <!-- Layout height: notification_min_height + bottom_stack_peek_amount -->
@@ -90,12 +91,6 @@
             layout="@layout/keyguard_bottom_area"
             android:visibility="gone" />
 
-    <ViewStub
-        android:id="@+id/status_bar_header"
-        android:layout_width="@dimen/notification_panel_width"
-        android:layout_height="@dimen/status_bar_header_height"
-        android:layout_gravity="@integer/notification_panel_layout_gravity" />
-
     <com.android.systemui.statusbar.AlphaOptimizedView
         android:id="@+id/qs_navbar_scrim"
         android:layout_height="96dp"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index cfe8d07..5c2b642 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -16,19 +16,38 @@
 
 package com.android.systemui.qs;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
-
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.BaseStatusBarHeader;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
- * Wrapper view with background which contains {@link QSPanel}
+ * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
+ *
+ * Also manages animations for the QS Header and Panel.
  */
 public class QSContainer extends FrameLayout {
+    private static final String TAG = "QSContainer";
+    private static final boolean DEBUG = false;
 
     private int mHeightOverride = -1;
     private QSPanel mQSPanel;
+    protected BaseStatusBarHeader mHeader;
+    private float mQsExpansion;
+    private boolean mQsExpanded;
+    private boolean mHeaderAnimating;
+    private boolean mKeyguardShowing;
+    private boolean mStackScrollerOverscrolling;
+
+    private long mDelay;
 
     public QSContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -38,6 +57,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+        mHeader = (BaseStatusBarHeader) findViewById(R.id.header);
     }
 
     @Override
@@ -63,14 +83,133 @@
      */
     public int getDesiredHeight() {
         if (mQSPanel.isClosingDetail()) {
-            return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom();
+            return mQSPanel.getGridHeight() + mHeader.getCollapsedHeight() + getPaddingBottom();
         } else {
             return getMeasuredHeight();
         }
     }
 
     private void updateBottom() {
-        int height = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        int height = (int) (mQsExpansion * (heightOverride - mHeader.getCollapsedHeight()))
+                + mHeader.getCollapsedHeight();
         setBottom(getTop() + height);
     }
+
+    private void updateQsState() {
+        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
+        mQSPanel.setExpanded(mQsExpanded);
+        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
+                ? View.VISIBLE
+                : View.INVISIBLE);
+        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
+                || (mQsExpanded && !mStackScrollerOverscrolling));
+        mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
+        setVisibility(mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
+    }
+
+    public BaseStatusBarHeader getHeader() {
+        return mHeader;
+    }
+
+    public QSPanel getQsPanel() {
+        return mQSPanel;
+    }
+
+    public void setHeaderClickable(boolean clickable) {
+        if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
+        mHeader.setClickable(clickable);
+    }
+
+    public void setExpanded(boolean expanded) {
+        if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
+        mQsExpanded = expanded;
+        updateQsState();
+    }
+
+    public void setKeyguardShowing(boolean keyguardShowing) {
+        if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
+        mKeyguardShowing = keyguardShowing;
+        updateQsState();
+    }
+
+    public void setOverscrolling(boolean stackScrollerOverscrolling) {
+        if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
+        mStackScrollerOverscrolling = stackScrollerOverscrolling;
+        updateQsState();
+    }
+
+    public void setListening(boolean listening) {
+        if (DEBUG) Log.d(TAG, "setListening " + listening);
+        mQSPanel.setListening(listening);
+        mHeader.setListening(listening);
+    }
+
+    public void setQsExpansion(float expansion, float headerTranslation) {
+        if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
+        mQsExpansion = expansion;
+        final float translationScaleY = expansion - 1;
+        if (!mHeaderAnimating) {
+            setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight())
+                    : headerTranslation);
+        }
+        mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
+        mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
+        updateBottom();
+    }
+
+    public void animateHeaderSlidingIn(long delay) {
+        if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
+        // If the QS is already expanded we don't need to slide in the header as it's already
+        // visible.
+        if (!mQsExpanded) {
+            mHeaderAnimating = true;
+            mDelay = delay;
+            getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
+        }
+    }
+
+    public void animateHeaderSlidingOut() {
+        if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
+        mHeaderAnimating = true;
+        animate().y(-mHeader.getHeight())
+                .setStartDelay(0)
+                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        animate().setListener(null);
+                        mHeaderAnimating = false;
+                        updateQsState();
+                    }
+                })
+                .start();
+    }
+
+    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
+            = new ViewTreeObserver.OnPreDrawListener() {
+        @Override
+        public boolean onPreDraw() {
+            getViewTreeObserver().removeOnPreDrawListener(this);
+            animate()
+                    .translationY(0f)
+                    .setStartDelay(mDelay)
+                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .setListener(mAnimateHeaderSlidingInListener)
+                    .start();
+            setY(-mHeader.getHeight());
+            return true;
+        }
+    };
+
+    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
+            = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mHeaderAnimating = false;
+            updateQsState();
+        }
+    };
 }
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 f822bd5..24e2678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
@@ -35,13 +34,11 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 import android.widget.TextView;
-
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.DejankUtils;
@@ -51,7 +48,6 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.qs.QSContainer;
-import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -91,11 +87,9 @@
     public static final long DOZE_ANIMATION_DURATION = 700;
 
     private KeyguardAffordanceHelper mAfforanceHelper;
-    protected BaseStatusBarHeader mHeader;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private QSContainer mQsContainer;
-    private QSPanel mQsPanel;
     private KeyguardStatusView mKeyguardStatusView;
     private ObservableScrollView mScrollView;
     private TextView mClockView;
@@ -175,8 +169,6 @@
     private Runnable mLaunchAnimationEndRunnable;
     private boolean mOnlyAffordanceInThisMotion;
     private boolean mKeyguardStatusViewAnimating;
-    private boolean mHeaderAnimating;
-    private ObjectAnimator mQsContainerAnimator;
     private ValueAnimator mQsSizeChangeAnimator;
 
     private boolean mShadeEmpty;
@@ -223,14 +215,10 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        ViewStub stub = (ViewStub) findViewById(R.id.status_bar_header);
-        stub.setLayoutResource(R.layout.quick_status_bar_expanded_header);
-        mHeader = (BaseStatusBarHeader) stub.inflate();
-        mHeader.setOnClickListener(this);
         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
         mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
-        mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+        mQsContainer.getHeader().setOnClickListener(this);
         mClockView = (TextView) findViewById(R.id.clock_view);
         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
         mScrollView.setListener(this);
@@ -285,12 +273,12 @@
     public void updateResources() {
         int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
         int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsContainer.getLayoutParams();
         if (lp.width != panelWidth) {
             lp.width = panelWidth;
             lp.gravity = panelGravity;
-            mHeader.setLayoutParams(lp);
-            mHeader.post(mUpdateHeader);
+            mQsContainer.setLayoutParams(lp);
+            mQsContainer.post(mUpdateHeader);
         }
 
         lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
@@ -318,8 +306,8 @@
 
         // Calculate quick setting heights.
         int oldMaxHeight = mQsMaxExpansionHeight;
-        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
-        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
+        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQsContainer.getHeader().getHeight();
+        mQsMaxExpansionHeight = mQsContainer.getDesiredHeight();
         positionClockAndNotifications();
         if (mQsExpanded && mQsFullyExpanded) {
             mQsExpansionHeight = mQsMaxExpansionHeight;
@@ -361,7 +349,7 @@
                 requestScrollerTopPaddingUpdate(false /* animate */);
                 requestPanelHeightUpdate();
                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
-                mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
+                mQsContainer.setHeightOverride(height);
             }
         });
         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
@@ -381,7 +369,7 @@
         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         int stackScrollerPadding;
         if (mStatusBarState != StatusBarState.KEYGUARD) {
-            int bottom = mHeader.getCollapsedHeight();
+            int bottom = mQsContainer.getHeader().getHeight();
             stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
                     ? bottom + mQsPeekHeight
                     : mKeyguardStatusBar.getHeight();
@@ -485,7 +473,7 @@
 
     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
         mQsExpansionEnabled = qsExpansionEnabled;
-        mHeader.setClickable(qsExpansionEnabled);
+        mQsContainer.setHeaderClickable(qsExpansionEnabled);
     }
 
     @Override
@@ -948,7 +936,7 @@
             amount = 0f;
         }
         float rounded = amount >= 1f ? amount : 0f;
-        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
+        setOverScrolling(rounded != 0f && isRubberbanded);
         mQsExpansionFromOverscroll = rounded != 0f;
         mLastOverscroll = rounded;
         updateQsState();
@@ -964,12 +952,17 @@
                     @Override
                     public void run() {
                         mStackScrollerOverscrolling = false;
-                        mQsExpansionFromOverscroll = false;
+                        setOverScrolling(false);
                         updateQsState();
                     }
                 }, false /* isClick */);
     }
 
+    private void setOverScrolling(boolean overscrolling) {
+        mStackScrollerOverscrolling = overscrolling;
+        mQsContainer.setOverscrolling(overscrolling);
+    }
+
     private void onQsExpansionStarted() {
         onQsExpansionStarted(0);
     }
@@ -997,7 +990,6 @@
             mFalsingManager.setQsExpanded(expanded);
             mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
             mStatusBar.setQsExpanded(expanded);
-            mQsPanel.setExpanded(expanded);
             mNotificationContainerParent.setQsExpanded(expanded);
         }
     }
@@ -1011,15 +1003,18 @@
 
         mStatusBarState = statusBarState;
         mKeyguardShowing = keyguardShowing;
+        mQsContainer.setKeyguardShowing(mKeyguardShowing);
 
         if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
                 && statusBarState == StatusBarState.SHADE_LOCKED)) {
             animateKeyguardStatusBarOut();
-            animateHeaderSlidingIn();
+            long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
+                    ? 0 : mStatusBar.calculateGoingToFullShadeDelay();
+            mQsContainer.animateHeaderSlidingIn(delay);
         } else if (oldState == StatusBarState.SHADE_LOCKED
                 && statusBarState == StatusBarState.KEYGUARD) {
             animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            animateHeaderSlidingOut();
+            mQsContainer.animateHeaderSlidingOut();
         } else {
             mKeyguardStatusBar.setAlpha(1f);
             mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -1050,95 +1045,6 @@
         }
     };
 
-    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
-            = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mHeaderAnimating = false;
-            mQsContainerAnimator = null;
-            mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
-        }
-    };
-
-    private final OnLayoutChangeListener mQsContainerAnimatorUpdater
-            = new OnLayoutChangeListener() {
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                int oldTop, int oldRight, int oldBottom) {
-            int oldHeight = oldBottom - oldTop;
-            int height = bottom - top;
-            if (height != oldHeight && mQsContainerAnimator != null) {
-                PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
-                float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
-                float newStartValue = -height - top;
-                values[0].setFloatValues(newStartValue, newEndValue);
-                mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
-            }
-        }
-    };
-
-    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
-            = new ViewTreeObserver.OnPreDrawListener() {
-        @Override
-        public boolean onPreDraw() {
-            getViewTreeObserver().removeOnPreDrawListener(this);
-            long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
-                    ? 0
-                    : mStatusBar.calculateGoingToFullShadeDelay();
-            mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
-            mHeader.animate()
-                    .translationY(0f)
-                    .setStartDelay(delay)
-                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .start();
-            mQsContainer.setY(-mQsContainer.getHeight());
-            mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
-                    mQsContainer.getTranslationY(),
-                    mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
-                            - mQsContainer.getTop());
-            mQsContainerAnimator.setStartDelay(delay);
-            mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
-            mQsContainerAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-            mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
-            mQsContainerAnimator.start();
-            mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
-            return true;
-        }
-    };
-
-    private void animateHeaderSlidingIn() {
-        // If the QS is already expanded we don't need to slide in the header as it's already
-        // visible.
-        if (!mQsExpanded) {
-            mHeaderAnimating = true;
-            getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
-        }
-    }
-
-    private void animateHeaderSlidingOut() {
-        mHeaderAnimating = true;
-        mHeader.animate().y(-mHeader.getHeight())
-                .setStartDelay(0)
-                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mHeader.animate().setListener(null);
-                        mHeaderAnimating = false;
-                        updateQsState();
-                    }
-                })
-                .start();
-        mQsContainer.animate()
-                .y(-mQsContainer.getHeight())
-                .setStartDelay(0)
-                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .start();
-    }
-
     private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
         @Override
         public void run() {
@@ -1262,18 +1168,10 @@
     }
 
     private void updateQsState() {
-        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
-        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
-                ? View.VISIBLE
-                : View.INVISIBLE);
-        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
-                || (mQsExpanded && !mStackScrollerOverscrolling));
+        mQsContainer.setExpanded(mQsExpanded);
         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);
         updateEmptyShadeView();
         mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
@@ -1298,11 +1196,10 @@
             }
         }
         mQsExpansionHeight = height;
-        mHeader.setExpansion(getHeaderExpansionFraction());
-        setQsTranslation(height);
+        updateQsExpansion();
         requestScrollerTopPaddingUpdate(false /* animate */);
         if (mKeyguardShowing) {
-            updateHeaderKeyguard();
+            updateHeaderKeyguardAlpha();
         }
         if (mStatusBarState == StatusBarState.SHADE_LOCKED
                 || mStatusBarState == StatusBarState.KEYGUARD) {
@@ -1329,6 +1226,10 @@
         }
     }
 
+    private void updateQsExpansion() {
+        mQsContainer.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
+    }
+
     private String getKeyguardOrLockScreenString() {
         if (mStatusBarState == StatusBarState.KEYGUARD) {
             return getContext().getString(R.string.accessibility_desc_lock_screen);
@@ -1337,23 +1238,6 @@
         }
     }
 
-    private float getHeaderExpansionFraction() {
-        if (!mKeyguardShowing) {
-            return getQsExpansionFraction();
-        } else {
-            return 1f;
-        }
-    }
-
-    private void setQsTranslation(float height) {
-        if (!mHeaderAnimating) {
-            mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
-        }
-        if (mKeyguardShowing && !mHeaderAnimating) {
-            mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
-        }
-    }
-
     private float calculateQsTopPadding() {
         if (mKeyguardShowing
                 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
@@ -1478,7 +1362,7 @@
         if (!mQsExpansionEnabled || mCollapsedOnDown) {
             return false;
         }
-        View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
+        View header = mKeyguardShowing ? mKeyguardStatusBar : mQsContainer;
         boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
                 && y >= header.getTop() && y <= header.getBottom();
         if (mQsExpanded) {
@@ -1678,18 +1562,9 @@
      */
     private void updateHeader() {
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
-            updateHeaderKeyguard();
-        } else {
-            updateHeaderShade();
+            updateHeaderKeyguardAlpha();
         }
-
-    }
-
-    private void updateHeaderShade() {
-        if (!mHeaderAnimating) {
-            mHeader.setTranslationY(getHeaderTranslation());
-        }
-        setQsTranslation(mQsExpansionHeight);
+        updateQsExpansion();
     }
 
     private float getHeaderTranslation() {
@@ -1744,11 +1619,6 @@
                 && !mDozing ? VISIBLE : INVISIBLE);
     }
 
-    private void updateHeaderKeyguard() {
-        updateHeaderKeyguardAlpha();
-        setQsTranslation(mQsExpansionHeight);
-    }
-
     private void updateKeyguardBottomAreaAlpha() {
         float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction());
         mKeyguardBottomArea.setAlpha(alpha);
@@ -1811,9 +1681,8 @@
     }
 
     private void setListening(boolean listening) {
-        mHeader.setListening(listening);
+        mQsContainer.setListening(listening);
         mKeyguardStatusBar.setListening(listening);
-        mQsPanel.setListening(listening);
     }
 
     @Override
@@ -1931,7 +1800,7 @@
 
     @Override
     public void onClick(View v) {
-        if (v == mHeader) {
+        if (v == mQsContainer.getHeader()) {
             onQsExpansionStarted();
             if (mQsExpanded) {
                 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
@@ -2162,11 +2031,11 @@
     }
 
     public boolean isQsDetailShowing() {
-        return mQsPanel.isShowingDetail();
+        return mQsContainer.getQsPanel().isShowingDetail();
     }
 
     public void closeQsDetail() {
-        mQsPanel.closeDetail();
+        mQsContainer.getQsPanel().closeDetail();
     }
 
     @Override
@@ -2254,7 +2123,7 @@
     private final Runnable mUpdateHeader = new Runnable() {
         @Override
         public void run() {
-            mHeader.updateEverything();
+            mQsContainer.getHeader().updateEverything();
         }
     };
 
@@ -2401,7 +2270,7 @@
     protected void setVerticalPanelTranslation(float translation) {
         mNotificationStackScroller.setTranslationX(translation);
         mScrollView.setTranslationX(translation);
-        mHeader.setTranslationX(translation);
+        mQsContainer.setTranslationX(translation);
     }
 
     private void updateStackHeight(float stackHeight) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d2bcf13..36ddbcf 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1265,8 +1265,16 @@
          * Use this when you just want to know if notifications are OK for this package.
          */
         @Override
+        public boolean areNotificationsEnabled(String pkg) {
+            return areNotificationsEnabledForPackage(pkg, Binder.getCallingUid());
+        }
+
+        /**
+         * Use this when you just want to know if notifications are OK for this package.
+         */
+        @Override
         public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
-            checkCallerIsSystem();
+            checkCallerIsSystemOrSameApp(pkg);
             return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
                     == AppOpsManager.MODE_ALLOWED) && !isApplicationSuspended(pkg, uid);
         }
@@ -1330,6 +1338,12 @@
         }
 
         @Override
+        public int getTopicImportance(String pkg, String topicId) {
+            checkCallerIsSystemOrSameApp(pkg);
+            return mRankingHelper.getImportance(pkg, Binder.getCallingUid(), topicId);
+        }
+
+        @Override
         public int getImportance(String pkg, int uid, Notification.Topic topic) {
             checkCallerIsSystem();
             return mRankingHelper.getImportance(pkg, uid, topic);
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 1a7e355..17bb907 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,4 +39,6 @@
     boolean doesAppUseTopics(String packageName, int uid);
 
     boolean hasBannedTopics(String packageName, int uid);
+
+    int getImportance(String packageName, int uid, String topicId);
 }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index aa36e29..6554bf9 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -467,6 +468,24 @@
     }
 
     /**
+     * Gets the importance of a topic. Unlike {@link #getImportance(String, int, String)}, does not
+     * create package or topic records if they don't exist.
+     */
+    @Override
+    public int getImportance(String packageName, int uid, String topicId) {
+        final String key = recordKey(packageName, uid);
+        Record r = mRecords.get(key);
+        if (r == null) {
+            return Ranking.IMPORTANCE_UNSPECIFIED;
+        }
+        Topic t = r.topics.get(topicId);
+        if (t == null) {
+            return Ranking.IMPORTANCE_UNSPECIFIED;
+        }
+        return t.importance;
+    }
+
+    /**
      * Gets importance. If a topic is given, returns the importance of that topic. Otherwise, the
      * importance of the app.
      */
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 3a30230..46de201 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -37,6 +37,7 @@
 
 // private NM API
 import android.app.INotificationManager;
+import android.widget.Toast;
 
 public class NotificationTestList extends TestActivity
 {
@@ -233,6 +234,30 @@
                 }
             },
 
+            new Test("Is blocked?") {
+                public void run() {
+                    Toast.makeText(NotificationTestList.this,
+                            "package enabled? " + mNM.areNotificationsEnabled(),
+                            Toast.LENGTH_LONG).show();
+                }
+            },
+
+            new Test("Topic banana importance?") {
+                public void run() {
+                    Toast.makeText(NotificationTestList.this,
+                            "bananas importance? " + mNM.getImportance("bananas"),
+                            Toast.LENGTH_LONG).show();
+                }
+            },
+
+            new Test("Topic garbage importance?") {
+                public void run() {
+                    Toast.makeText(NotificationTestList.this,
+                            "garbage importance? " + mNM.getImportance("garbage"),
+                            Toast.LENGTH_LONG).show();
+                }
+            },
+
         new Test("Whens") {
             public void run()
             {