Merge "decouple Bubble from NotificationEntry" into rvc-dev
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
index ec7ba28..96bb5ef 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
@@ -44,7 +44,7 @@
 /**
  * Class for representing how a blob can be shared.
  *
- * Note that this class is not thread-safe, callers need to take of synchronizing access.
+ * Note that this class is not thread-safe, callers need to take care of synchronizing access.
  */
 class BlobAccessMode {
     @Retention(RetentionPolicy.SOURCE)
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index cea7fcc..03573d9 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -61,6 +61,8 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.blob.BlobStoreManagerService.DumpArgs;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -349,14 +351,16 @@
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
         }
-        synchronized (mMetadataLock) {
-            return createRevocableFdLocked(fd, callingPackage);
+        try {
+            return createRevocableFd(fd, callingPackage);
+        } catch (IOException e) {
+            IoUtils.closeQuietly(fd);
+            throw e;
         }
     }
 
-    @GuardedBy("mMetadataLock")
     @NonNull
-    private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd,
+    private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
             String callingPackage) throws IOException {
         final RevocableFileDescriptor revocableFd =
                 new RevocableFileDescriptor(mContext, fd);
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 22d5d11..a10b0b3 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -59,6 +59,8 @@
 import com.android.server.blob.BlobStoreManagerService.DumpArgs;
 import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -207,27 +209,37 @@
                 throw new IllegalStateException("Not allowed to write in state: "
                         + stateToString(mState));
             }
+        }
 
-            try {
-                return openWriteLocked(offsetBytes, lengthBytes);
-            } catch (IOException e) {
-                throw ExceptionUtils.wrap(e);
+        FileDescriptor fd = null;
+        try {
+            fd = openWriteInternal(offsetBytes, lengthBytes);
+            final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd);
+            synchronized (mSessionLock) {
+                if (mState != STATE_OPENED) {
+                    IoUtils.closeQuietly(fd);
+                    throw new IllegalStateException("Not allowed to write in state: "
+                            + stateToString(mState));
+                }
+                trackRevocableFdLocked(revocableFd);
+                return revocableFd.getRevocableFileDescriptor();
             }
+        } catch (IOException e) {
+            IoUtils.closeQuietly(fd);
+            throw ExceptionUtils.wrap(e);
         }
     }
 
-    @GuardedBy("mSessionLock")
     @NonNull
-    private ParcelFileDescriptor openWriteLocked(@BytesLong long offsetBytes,
+    private FileDescriptor openWriteInternal(@BytesLong long offsetBytes,
             @BytesLong long lengthBytes) throws IOException {
         // TODO: Add limit on active open sessions/writes/reads
-        FileDescriptor fd = null;
         try {
             final File sessionFile = getSessionFile();
             if (sessionFile == null) {
                 throw new IllegalStateException("Couldn't get the file for this session");
             }
-            fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600);
+            final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600);
             if (offsetBytes > 0) {
                 final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET);
                 if (curOffset != offsetBytes) {
@@ -238,10 +250,10 @@
             if (lengthBytes > 0) {
                 mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes);
             }
+            return fd;
         } catch (ErrnoException e) {
-            e.rethrowAsIOException();
+            throw e.rethrowAsIOException();
         }
-        return createRevocableFdLocked(fd);
     }
 
     @Override
@@ -253,29 +265,40 @@
                 throw new IllegalStateException("Not allowed to read in state: "
                         + stateToString(mState));
             }
+        }
 
-            try {
-                return openReadLocked();
-            } catch (IOException e) {
-                throw ExceptionUtils.wrap(e);
+        FileDescriptor fd = null;
+        try {
+            fd = openReadInternal();
+            final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd);
+            synchronized (mSessionLock) {
+                if (mState != STATE_OPENED) {
+                    IoUtils.closeQuietly(fd);
+                    throw new IllegalStateException("Not allowed to read in state: "
+                            + stateToString(mState));
+                }
+                trackRevocableFdLocked(revocableFd);
+                return revocableFd.getRevocableFileDescriptor();
             }
+
+        } catch (IOException e) {
+            IoUtils.closeQuietly(fd);
+            throw ExceptionUtils.wrap(e);
         }
     }
 
-    @GuardedBy("mSessionLock")
     @NonNull
-    private ParcelFileDescriptor openReadLocked() throws IOException {
-        FileDescriptor fd = null;
+    private FileDescriptor openReadInternal() throws IOException {
         try {
             final File sessionFile = getSessionFile();
             if (sessionFile == null) {
                 throw new IllegalStateException("Couldn't get the file for this session");
             }
-            fd = Os.open(sessionFile.getPath(), O_RDONLY, 0);
+            final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0);
+            return fd;
         } catch (ErrnoException e) {
-            e.rethrowAsIOException();
+            throw e.rethrowAsIOException();
         }
-        return createRevocableFdLocked(fd);
     }
 
     @Override
@@ -396,7 +419,7 @@
             }
 
             mState = state;
-            revokeAllFdsLocked();
+            revokeAllFds();
 
             if (sendCallback) {
                 mListener.onStateChanged(this);
@@ -433,20 +456,17 @@
         }
     }
 
-    @GuardedBy("mSessionLock")
-    private void revokeAllFdsLocked() {
-        for (int i = mRevocableFds.size() - 1; i >= 0; --i) {
-            mRevocableFds.get(i).revoke();
+    private void revokeAllFds() {
+        synchronized (mRevocableFds) {
+            for (int i = mRevocableFds.size() - 1; i >= 0; --i) {
+                mRevocableFds.get(i).revoke();
+            }
+            mRevocableFds.clear();
         }
-        mRevocableFds.clear();
     }
 
     @GuardedBy("mSessionLock")
-    @NonNull
-    private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd)
-            throws IOException {
-        final RevocableFileDescriptor revocableFd =
-                new RevocableFileDescriptor(mContext, fd);
+    private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) {
         synchronized (mRevocableFds) {
             mRevocableFds.add(revocableFd);
         }
@@ -455,7 +475,6 @@
                 mRevocableFds.remove(revocableFd);
             }
         });
-        return revocableFd.getRevocableFileDescriptor();
     }
 
     @Nullable
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 47bab29..6f952f6 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -267,8 +267,11 @@
     for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) {
         uint64_t reportsListToken =
                 proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST);
+        // Don't include the current bucket to avoid skipping buckets.
+        // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS
+        // or other alternatives to avoid skipping buckets for pulled metrics.
         mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(),
-                                 true /* includeCurrentBucket */, false /* erase_data */,
+                                 false /* includeCurrentBucket */, false /* erase_data */,
                                  ADB_DUMP,
                                  FAST,
                                  &proto);
diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
index db402a0..32cecd3 100644
--- a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
+++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
@@ -50,13 +50,13 @@
     });
 
     vector<thread> threads;
-    vector<bool> done(numConditions, false);
+    vector<int> done(numConditions, 0);
 
     int i = 0;
     for (const string& conditionName : conditionNames) {
         threads.emplace_back([&done, &conditionName, &trigger, i] {
             sleep_for(chrono::milliseconds(3));
-            done[i] = true;
+            done[i] = 1;
             trigger.markComplete(conditionName);
         });
         i++;
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index d22f942..3f03f2a 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -71,6 +71,4 @@
 
     void reportActivityView(in IInputMethodClient parentClient, int childDisplayId,
             in float[] matrixValues);
-
-    void removeImeSurface();
 }
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 4bf0fca..cf967c0 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -118,4 +118,7 @@
         <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item>
         <item>com.android.systemui.car.volume.VolumeUI</item>
     </string-array>
+
+    <!-- How many milliseconds to wait before force hiding the UserSwitchTransitionView -->
+    <integer name="config_userSwitchTransitionViewShownTimeoutMs" translatable="false">5000</integer>
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
index 8aa7b63..45f3d34 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
@@ -33,6 +33,7 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.R;
 import com.android.systemui.car.window.OverlayViewController;
@@ -49,12 +50,22 @@
 public class UserSwitchTransitionViewController extends OverlayViewController {
     private static final String TAG = "UserSwitchTransition";
     private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
+    private static final boolean DEBUG = false;
 
     private final Context mContext;
     private final Handler mHandler;
     private final Resources mResources;
     private final UserManager mUserManager;
     private final IWindowManager mWindowManagerService;
+    private final int mWindowShownTimeoutMs;
+    private final Runnable mWindowShownTimeoutCallback = () -> {
+        if (DEBUG) {
+            Log.w(TAG, "Window was not hidden within " + getWindowShownTimeoutMs() + " ms, so it"
+                    + "was hidden by mWindowShownTimeoutCallback.");
+        }
+
+        handleHide();
+    };
 
     @GuardedBy("this")
     private boolean mShowing;
@@ -76,6 +87,8 @@
         mResources = resources;
         mUserManager = userManager;
         mWindowManagerService = windowManagerService;
+        mWindowShownTimeoutMs = mResources.getInteger(
+                R.integer.config_userSwitchTransitionViewShownTimeoutMs);
     }
 
     /**
@@ -98,6 +111,9 @@
             populateDialog(mPreviousUserId, newUserId);
             // next time a new user is selected, this current new user will be the previous user.
             mPreviousUserId = newUserId;
+            // In case the window is still showing after WINDOW_SHOWN_TIMEOUT_MS, then hide the
+            // window and log a warning message.
+            mHandler.postDelayed(mWindowShownTimeoutCallback, mWindowShownTimeoutMs);
         });
     }
 
@@ -105,6 +121,12 @@
         if (!mShowing) return;
         mShowing = false;
         mHandler.post(this::stop);
+        mHandler.removeCallbacks(mWindowShownTimeoutCallback);
+    }
+
+    @VisibleForTesting
+    int getWindowShownTimeoutMs() {
+        return mWindowShownTimeoutMs;
     }
 
     private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
index 65c5562..797dbf5 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
@@ -18,6 +18,8 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -122,6 +124,29 @@
                 any());
     }
 
+    @Test
+    public void onWindowShownTimeoutPassed_viewNotHidden_hidesUserSwitchTransitionView() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+        reset(mOverlayViewGlobalStateController);
+
+        getContext().getMainThreadHandler().postDelayed(() -> {
+            verify(mOverlayViewGlobalStateController).hideView(
+                    eq(mCarUserSwitchingDialogController), any());
+        }, mCarUserSwitchingDialogController.getWindowShownTimeoutMs() + 10);
+    }
+
+    @Test
+    public void onWindowShownTimeoutPassed_viewHidden_doesNotHideUserSwitchTransitionViewAgain() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+        mCarUserSwitchingDialogController.handleHide();
+        reset(mOverlayViewGlobalStateController);
+
+        getContext().getMainThreadHandler().postDelayed(() -> {
+            verify(mOverlayViewGlobalStateController, never()).hideView(
+                    eq(mCarUserSwitchingDialogController), any());
+        }, mCarUserSwitchingDialogController.getWindowShownTimeoutMs() + 10);
+    }
+
     private final class TestableUserSwitchTransitionViewController extends
             UserSwitchTransitionViewController {
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 85a9fec..fffcafb 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -34,7 +34,7 @@
 
     String ACTION = "com.android.systemui.action.PLUGIN_QS";
 
-    int VERSION = 7;
+    int VERSION = 8;
 
     String TAG = "QS";
 
@@ -67,15 +67,12 @@
     }
 
     /**
-     * We need this to handle nested scrolling for QS..
-     * Normally we would do this with requestDisallowInterceptTouchEvent, but when both the
-     * scroll containers are using the same touch slop, they try to start scrolling at the
-     * same time and NotificationPanelView wins, this lets QS win.
-     *
-     * TODO: Do this using NestedScroll capabilities.
+     * Should touches from the notification panel be disallowed?
+     * The notification panel might grab any touches rom QS at any time to collapse the shade.
+     * We should disallow that in case we are showing the detail panel.
      */
-    default boolean onInterceptTouchEvent(MotionEvent event) {
-        return isCustomizing();
+    default boolean disallowPanelTouches() {
+        return isShowingDetail();
     }
 
     @ProvidesInterface(version = HeightListener.VERSION)
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 294bd50..59e1a75 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -22,6 +22,7 @@
     android:background="@drawable/qs_detail_background"
     android:clickable="true"
     android:orientation="vertical"
+    android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
     android:paddingBottom="8dp"
     android:visibility="invisible"
     android:elevation="4dp"
@@ -44,7 +45,7 @@
         android:scaleType="fitXY"
         />
 
-    <com.android.systemui.qs.NonInterceptingScrollView
+    <ScrollView
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
@@ -54,7 +55,7 @@
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"/>
-    </com.android.systemui.qs.NonInterceptingScrollView>
+    </ScrollView>
 
     <include layout="@layout/qs_detail_buttons" />
 
diff --git a/packages/SystemUI/res/layout/qs_divider.xml b/packages/SystemUI/res/layout/qs_divider.xml
deleted file mode 100644
index 39d48ea..0000000
--- a/packages/SystemUI/res/layout/qs_divider.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="1dp"
-    android:alpha=".12"
-    android:background="?android:attr/colorForeground" />
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index ebfd0a0..5c00af5 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -61,7 +61,10 @@
                 android:clickable="true"
                 android:gravity="center_vertical"
                 android:focusable="true"
+                android:singleLine="true"
+                android:ellipsize="end"
                 android:textAppearance="@style/TextAppearance.QS.Status"
+                android:layout_marginEnd="4dp"
                 android:visibility="gone"/>
          </com.android.keyguard.AlphaOptimizedLinearLayout>
 
diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml
index a8960d9..5c8b2b0 100644
--- a/packages/SystemUI/res/layout/qs_paged_page.xml
+++ b/packages/SystemUI/res/layout/qs_paged_page.xml
@@ -20,7 +20,5 @@
     android:id="@+id/tile_page"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingStart="@dimen/notification_side_paddings"
-    android:paddingEnd="@dimen/notification_side_paddings"
     android:clipChildren="false"
     android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index cdf8426..761ab03 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -48,18 +48,22 @@
         android:clipChildren="false"
         android:background="@drawable/qs_bg_gradient" />
 
-
-    <com.android.systemui.qs.QSPanel
-        android:id="@+id/quick_settings_panel"
-        android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
+    <com.android.systemui.qs.NonInterceptingScrollView
+        android:id="@+id/expanded_qs_scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:elevation="4dp"
-        android:background="@android:color/transparent"
-        android:focusable="true"
-        android:accessibilityTraversalBefore="@android:id/edit">
-        <include layout="@layout/qs_footer_impl" />
-    </com.android.systemui.qs.QSPanel>
+        android:layout_weight="1">
+        <com.android.systemui.qs.QSPanel
+            android:id="@+id/quick_settings_panel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@android:color/transparent"
+            android:focusable="true"
+            android:accessibilityTraversalBefore="@android:id/edit">
+            <include layout="@layout/qs_footer_impl" />
+        </com.android.systemui.qs.QSPanel>
+    </com.android.systemui.qs.NonInterceptingScrollView>
 
     <include layout="@layout/quick_status_bar_expanded_header" />
 
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index 15f398a..e7c7b5f 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -14,8 +14,8 @@
      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"
+<com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:clickable="true"
     android:paddingBottom="@dimen/qs_tile_padding_top"
@@ -23,23 +23,28 @@
     android:paddingStart="@dimen/qs_footer_padding_start"
     android:paddingEnd="@dimen/qs_footer_padding_end"
     android:gravity="center_vertical"
+    android:layout_gravity="center_vertical|center_horizontal"
     android:background="@android:color/transparent">
 
-    <TextView
+    <com.android.systemui.util.AutoMarqueeTextView
         android:id="@+id/footer_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="start"
         android:layout_weight="1"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:marqueeRepeatLimit="marquee_forever"
         android:textAppearance="@style/TextAppearance.QS.TileLabel"
-        style="@style/qs_security_footer"/>
+        android:textColor="?android:attr/textColorPrimary"/>
 
     <ImageView
         android:id="@+id/footer_icon"
         android:layout_width="@dimen/qs_footer_icon_size"
         android:layout_height="@dimen/qs_footer_icon_size"
+        android:layout_marginStart="8dp"
         android:contentDescription="@null"
         android:src="@drawable/ic_info_outline"
-        style="@style/qs_security_footer"/>
+        android:tint="?android:attr/textColorPrimary" />
 
-</LinearLayout>
+</com.android.systemui.util.NeverExactlyLinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml
index e6ef9b4..fb82304 100644
--- a/packages/SystemUI/res/layout/quick_settings_header_info.xml
+++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml
@@ -20,11 +20,12 @@
     android:layout_height="@dimen/qs_header_tooltip_height"
     android:layout_below="@id/quick_status_bar_system_icons"
     android:visibility="invisible"
-    android:theme="@style/QSHeaderTheme">
+    android:theme="@style/QSHeaderTheme"
+    android:forceHasOverlappingRendering="false">
 
         <com.android.systemui.qs.QSHeaderInfoLayout
             android:id="@+id/status_container"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_weight="1"
             android:layout_height="match_parent">
 
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index abeb331..dc341274 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -29,7 +29,6 @@
     android:clipToPadding="false"
     android:paddingTop="0dp"
     android:paddingEnd="0dp"
-    android:paddingBottom="10dp"
     android:paddingStart="0dp"
     android:elevation="4dp" >
 
@@ -52,6 +51,7 @@
         android:clipChildren="false"
         android:clipToPadding="false"
         android:focusable="true"
+        android:paddingBottom="10dp"
         android:importantForAccessibility="yes" />
 
     <TextView
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index d118d89..2d42ce6 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -31,7 +31,6 @@
     <dimen name="battery_detail_graph_space_bottom">9dp</dimen>
 
     <integer name="quick_settings_num_columns">4</integer>
-    <bool name="quick_settings_wide">true</bool>
     <dimen name="qs_detail_margin_top">0dp</dimen>
 
     <dimen name="volume_tool_tip_right_margin">136dp</dimen>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 50261e1..4fdeb6f 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -29,9 +29,4 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="qs_security_footer" parent="@style/qs_theme">
-        <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white -->
-        <item name="android:tint">#FFFFFFFF</item>
-    </style>
-
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index c4c4671..3b00ad1 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,8 +15,5 @@
   ~ limitations under the License
   -->
 <resources>
-    <!-- Whether QuickSettings is in a phone landscape -->
-    <bool name="quick_settings_wide">false</bool>
-
     <integer name="quick_settings_num_columns">3</integer>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
new file mode 100644
index 0000000..40838f3
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+<resources>
+    <!-- Size of the panel of large phones on portrait. This shouldn't fill, but have some padding on the side -->
+    <dimen name="notification_panel_width">416dp</dimen>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 8f73d23..fdf4e3b 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -16,8 +16,6 @@
 */
 -->
 <resources>
-    <!-- Standard notification width + gravity -->
-    <dimen name="notification_panel_width">416dp</dimen>
 
     <!-- Diameter of outer shape drawable shown in navbar search-->
     <dimen name="navbar_search_outerring_diameter">430dip</dimen>
diff --git a/packages/SystemUI/res/values-sw900dp-land/dimen.xml b/packages/SystemUI/res/values-sw900dp-land/dimen.xml
deleted file mode 100644
index 1e0600e..0000000
--- a/packages/SystemUI/res/values-sw900dp-land/dimen.xml
+++ /dev/null
@@ -1,23 +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.
-*/
--->
-<resources>
-    <!-- Standard notification width + gravity for tablet large screen device -->
-    <dimen name="notification_panel_width">544dp</dimen>
-
-</resources>
-
diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml
index 16d5317..a33f131 100644
--- a/packages/SystemUI/res/values-w550dp-land/config.xml
+++ b/packages/SystemUI/res/values-w550dp-land/config.xml
@@ -20,9 +20,5 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-
-    <!-- Whether QuickSettings is in a phone landscape -->
-    <bool name="quick_settings_wide">true</bool>
-
-    <integer name="quick_settings_num_columns">4</integer>
+    <integer name="quick_settings_num_columns">6</integer>
 </resources>
diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml
deleted file mode 100644
index 017ca69..0000000
--- a/packages/SystemUI/res/values-w550dp-land/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
-*/
--->
-<resources>
-    <!-- Standard notification width + gravity -->
-    <dimen name="notification_panel_width">544dp</dimen>
-
-</resources>
diff --git a/packages/SystemUI/res/values-w650dp-land/dimens.xml b/packages/SystemUI/res/values-w650dp-land/dimens.xml
new file mode 100644
index 0000000..108d6cf
--- /dev/null
+++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+<resources>
+    <!-- Standard notification width + gravity -->
+    <dimen name="notification_panel_width">644dp</dimen>
+
+</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4fc904b..40a4b50 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -203,8 +203,8 @@
     <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
 
     <!-- Bubbles -->
-    <color name="bubbles_pointer_light">#FFFFFF</color>
-    <color name="bubbles_pointer_dark">@color/GM2_grey_800</color>
+    <color name="bubbles_light">#FFFFFF</color>
+    <color name="bubbles_dark">@color/GM2_grey_800</color>
 
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 00537ff..848cdb1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -95,9 +95,6 @@
     <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_columns">6</integer>
 
-    <!-- Whether QuickSettings is in a phone landscape -->
-    <bool name="quick_settings_wide">false</bool>
-
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">3</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 44d3b02..d176fed 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1075,8 +1075,7 @@
     <dimen name="edge_margin">8dp</dimen>
 
     <!-- The absolute side margins of quick settings -->
-    <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen>
-    <dimen name="quick_settings_media_extra_bottom_margin">6dp</dimen>
+    <dimen name="quick_settings_bottom_margin_media">16dp</dimen>
     <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
     <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
@@ -1264,8 +1263,8 @@
     <dimen name="qs_media_panel_outer_padding">16dp</dimen>
     <dimen name="qs_media_album_size">52dp</dimen>
     <dimen name="qs_seamless_icon_size">20dp</dimen>
-    <dimen name="qqs_media_spacing">8dp</dimen>
-    <dimen name="qqs_horizonal_tile_padding_bottom">8dp</dimen>
+    <dimen name="qqs_media_spacing">16dp</dimen>
+    <dimen name="qs_footer_horizontal_margin">22dp</dimen>
 
     <dimen name="magnification_border_size">5dp</dimen>
     <dimen name="magnification_frame_move_short">5dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8097c01..68c2a38 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -387,11 +387,6 @@
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
     </style>
 
-    <style name="qs_security_footer" parent="@style/qs_theme">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
-        <item name="android:tint">?android:attr/textColorSecondary</item>
-    </style>
-
     <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:colorAccent">@color/remote_input_accent</item>
     </style>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 471a769..2f7ffde 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -437,10 +437,10 @@
                 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
         switch (mode) {
             case Configuration.UI_MODE_NIGHT_NO:
-                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_light));
+                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_light));
                 break;
             case Configuration.UI_MODE_NIGHT_YES:
-                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_dark));
+                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_dark));
                 break;
         }
         mPointerView.setBackground(mPointerDrawable);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index 0c62e9f..dadcb3a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -22,9 +22,9 @@
 import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
 
 import android.content.Context;
-import android.content.res.TypedArray;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Path;
 import android.graphics.drawable.AdaptiveIconDrawable;
@@ -88,19 +88,23 @@
                 false /* attachToRoot */);
         mOverflowBtn.setContentDescription(mContext.getResources().getString(
                 R.string.bubble_overflow_button_content_description));
+        Resources res = mContext.getResources();
 
-        TypedArray ta = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.colorBackgroundFloating});
-        int bgColor = ta.getColor(0, Color.WHITE /* default */);
-        ta.recycle();
-
+        // Set color for button icon and dot
         TypedValue typedValue = new TypedValue();
         mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
         int colorAccent = mContext.getColor(typedValue.resourceId);
         mOverflowBtn.getDrawable().setTint(colorAccent);
         mDotColor = colorAccent;
 
-        ColorDrawable bg = new ColorDrawable(bgColor);
+        // Set color for button and activity background
+        ColorDrawable bg = new ColorDrawable(res.getColor(R.color.bubbles_light));
+        final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        if (mode == Configuration.UI_MODE_NIGHT_YES) {
+            bg = new ColorDrawable(res.getColor(R.color.bubbles_dark));
+        }
+
+        // Apply icon inset
         InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(),
                 mBitmapSize - mIconBitmapSize /* inset */);
         AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg);
@@ -110,6 +114,7 @@
                 null /* user */,
                 true /* shrinkNonAdaptiveIcons */).icon;
 
+        // Get path with dot location
         float scale = iconFactory.getNormalizer().getScale(mOverflowBtn.getDrawable(),
                 null /* outBounds */, null /* path */, null /* outMaskShape */);
         float radius = DEFAULT_PATH_SIZE / 2f;
@@ -120,14 +125,9 @@
                 radius /* pivot y */);
         mPath.transform(matrix);
 
-        mOverflowBtn.setVisibility(GONE);
         mOverflowBtn.setRenderedBubble(this);
     }
 
-    ImageView getBtn() {
-        return mOverflowBtn;
-    }
-
     void setVisible(int visible) {
         mOverflowBtn.setVisibility(visible);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index ea324af..0b25c44 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -27,6 +27,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -100,7 +101,6 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.bubble_overflow_activity);
-        setBackgroundColor();
 
         mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
         mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
@@ -141,34 +141,25 @@
      * Handle theme changes.
      */
     void updateTheme() {
-        final int mode =
-                getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        Resources res = getResources();
+        final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
         switch (mode) {
-            case Configuration.UI_MODE_NIGHT_NO:
-                if (DEBUG_OVERFLOW) {
-                    Log.d(TAG, "Set overflow UI to light mode");
-                }
-                mEmptyStateImage.setImageDrawable(
-                        getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light));
-                break;
             case Configuration.UI_MODE_NIGHT_YES:
-                if (DEBUG_OVERFLOW) {
-                    Log.d(TAG, "Set overflow UI to dark mode");
-                }
                 mEmptyStateImage.setImageDrawable(
-                        getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
+                        res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
+                findViewById(android.R.id.content)
+                        .setBackgroundColor(res.getColor(R.color.bubbles_dark));
+                break;
+
+            case Configuration.UI_MODE_NIGHT_NO:
+                mEmptyStateImage.setImageDrawable(
+                        res.getDrawable(R.drawable.ic_empty_bubble_overflow_light));
+                findViewById(android.R.id.content)
+                        .setBackgroundColor(res.getColor(R.color.bubbles_light));
                 break;
         }
     }
 
-    void setBackgroundColor() {
-        final TypedArray ta = getApplicationContext().obtainStyledAttributes(
-                new int[]{android.R.attr.colorBackgroundFloating});
-        int bgColor = ta.getColor(0, Color.WHITE);
-        ta.recycle();
-        findViewById(android.R.id.content).setBackgroundColor(bgColor);
-    }
-
     void onDataChanged(List<Bubble> bubbles) {
         mOverflowBubbles.clear();
         mOverflowBubbles.addAll(bubbles);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index a59d2c0..5542592 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -533,6 +533,8 @@
                         mMagneticTarget,
                         mIndividualBubbleMagnetListener);
 
+                hideImeFromExpandedBubble();
+
                 // Save the magnetized individual bubble so we can dispatch touch events to it.
                 mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
             } else {
@@ -1094,13 +1096,17 @@
             mBubbleOverflow = new BubbleOverflow(getContext());
             mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
         } else {
-            mBubbleContainer.removeView(mBubbleOverflow.getBtn());
+            mBubbleContainer.removeView(mBubbleOverflow.getIconView());
             mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
             overflowBtnIndex = mBubbleContainer.getChildCount();
         }
-        mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
+        mBubbleContainer.addView(mBubbleOverflow.getIconView(), overflowBtnIndex,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-        mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow));
+        mBubbleOverflow.getIconView().setOnClickListener(v -> {
+            setSelectedBubble(mBubbleOverflow);
+            showManageMenu(false);
+        });
+        updateOverflowVisibility();
     }
     /**
      * Handle theme changes.
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 775a1649..b86e1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -480,7 +480,17 @@
             if (inOverlay) {
                 rootOverlay!!.add(mediaFrame)
             } else {
+                // When adding back to the host, let's make sure to reset the bounds.
+                // Usually adding the view will trigger a layout that does this automatically,
+                // but we sometimes suppress this.
                 targetHost.addView(mediaFrame)
+                val left = targetHost.paddingLeft
+                val top = targetHost.paddingTop
+                mediaFrame.setLeftTopRightBottom(
+                        left,
+                        top,
+                        left + currentBounds.width(),
+                        top + currentBounds.height())
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index 40d317c..dc157a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -139,12 +139,7 @@
         }
         tilesToShow = actualColumns * NUM_LINES
 
-        val interTileSpace = if (actualColumns <= 2) {
-            // Extra "column" of padding to be distributed on each end
-            (availableWidth - actualColumns * smallTileSize) / actualColumns
-        } else {
-            (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1)
-        }
+        val spacePerTile = availableWidth / actualColumns
 
         for (index in 0 until mRecords.size) {
             val tileView = mRecords[index].tileView
@@ -154,15 +149,16 @@
                 tileView.visibility = View.VISIBLE
                 if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView)
                 val column = index % actualColumns
-                val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2)
+                val left = getLeftForColumn(column, spacePerTile)
                 val top = if (index < actualColumns) 0 else getTopBottomRow()
                 tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
             }
         }
     }
 
-    private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int {
-        return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace)
+    private fun getLeftForColumn(column: Int, spacePerTile: Int): Int {
+        // Distribute the space evenly among all tiles.
+        return (column * spacePerTile + spacePerTile / 2.0f - smallTileSize / 2.0f).toInt()
     }
 
     private fun getTopBottomRow() = smallTileSize + cellMarginVertical
diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
index c204d94..aa17c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
@@ -17,6 +17,9 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
 import android.widget.ScrollView;
 
 /**
@@ -24,8 +27,12 @@
  */
 public class NonInterceptingScrollView extends ScrollView {
 
+    private final int mTouchSlop;
+    private float mDownY;
+
     public NonInterceptingScrollView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
     @Override
@@ -34,10 +41,52 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (canScrollVertically(1)) {
-                    requestDisallowInterceptTouchEvent(true);
+                    // If we can scroll down, make sure we're not intercepted by the parent
+                    final ViewParent parent = getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
                 }
                 break;
         }
         return super.onTouchEvent(ev);
     }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // If there's a touch on this view and we can scroll down, we don't want to be intercepted
+        int action = ev.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                // If we can scroll down, make sure non of our parents intercepts us.
+                if (canScrollVertically(1)) {
+                    final ViewParent parent = getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
+                }
+                mDownY = ev.getY();
+                break;
+            case MotionEvent.ACTION_MOVE: {
+                final int y = (int) ev.getY();
+                final float yDiff = y - mDownY;
+                if (yDiff < -mTouchSlop && !canScrollVertically(1)) {
+                    // Don't intercept touches that are overscrolling.
+                    return false;
+                }
+                break;
+            }
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    public int getScrollRange() {
+        int scrollRange = 0;
+        if (getChildCount() > 0) {
+            View child = getChildAt(0);
+            scrollRange = Math.max(0,
+                    child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
+        }
+        return scrollRange;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index aaff9ac..2836992 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -66,6 +66,10 @@
     private int mHorizontalClipBound;
     private final Rect mClippingRect;
     private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
+    private int mExcessHeight;
+    private int mLastExcessHeight;
+    private int mMinRows = 1;
+    private int mMaxColumns = TileLayout.NO_MAX_COLUMNS;
 
     public PagedTileLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -195,11 +199,18 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mPages.add((TilePage) LayoutInflater.from(getContext())
-                .inflate(R.layout.qs_paged_page, this, false));
+        mPages.add(createTilePage());
         mAdapter.notifyDataSetChanged();
     }
 
+    private TilePage createTilePage() {
+        TilePage page = (TilePage) LayoutInflater.from(getContext())
+                .inflate(R.layout.qs_paged_page, this, false);
+        page.setMinRows(mMinRows);
+        page.setMaxColumns(mMaxColumns);
+        return page;
+    }
+
     public void setPageIndicator(PageIndicator indicator) {
         mPageIndicator = indicator;
         mPageIndicator.setNumPages(mPages.size());
@@ -298,8 +309,7 @@
         }
         while (mPages.size() < numPages) {
             if (DEBUG) Log.d(TAG, "Adding page");
-            mPages.add((TilePage) LayoutInflater.from(getContext())
-                    .inflate(R.layout.qs_paged_page, this, false));
+            mPages.add(createTilePage());
         }
         while (mPages.size() > numPages) {
             if (DEBUG) Log.d(TAG, "Removing page");
@@ -342,17 +352,54 @@
     }
 
     @Override
+    public boolean setMinRows(int minRows) {
+        mMinRows = minRows;
+        boolean changed = false;
+        for (int i = 0; i < mPages.size(); i++) {
+            if (mPages.get(i).setMinRows(minRows)) {
+                changed = true;
+                mDistributeTiles = true;
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    public boolean setMaxColumns(int maxColumns) {
+        mMaxColumns = maxColumns;
+        boolean changed = false;
+        for (int i = 0; i < mPages.size(); i++) {
+            if (mPages.get(i).setMaxColumns(maxColumns)) {
+                changed = true;
+                mDistributeTiles = true;
+            }
+        }
+        return changed;
+    }
+
+    /**
+     * Set the amount of excess space that we gave this view compared to the actual available
+     * height. This is because this view is in a scrollview.
+     */
+    public void setExcessHeight(int excessHeight) {
+        mExcessHeight = excessHeight;
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 
         final int nTiles = mTiles.size();
         // If we have no reason to recalculate the number of rows, skip this step. In particular,
         // if the height passed by its parent is the same as the last time, we try not to remeasure.
-        if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)) {
+        if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)
+                || mLastExcessHeight != mExcessHeight) {
 
             mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec);
+            mLastExcessHeight = mExcessHeight;
             // Only change the pages if the number of rows or columns (from updateResources) has
             // changed or the tiles have changed
-            if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) {
+            int availableHeight = mLastMaxHeight - mExcessHeight;
+            if (mPages.get(0).updateMaxRows(availableHeight, nTiles) || mDistributeTiles) {
                 mDistributeTiles = false;
                 distributeTiles();
             }
@@ -485,14 +532,6 @@
             // up.
             return Math.max(mColumns * mRows, 1);
         }
-
-        @Override
-        public boolean updateResources() {
-            final int sidePadding = getContext().getResources().getDimensionPixelSize(
-                    R.dimen.notification_side_paddings);
-            setPadding(sidePadding, 0, sidePadding, 0);
-            return super.updateResources();
-        }
     }
 
     private final PagerAdapter mAdapter = new PagerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index ce00229..bc8f5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -18,6 +18,7 @@
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnLayoutChangeListener;
+import android.widget.ScrollView;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.qs.QS;
@@ -30,7 +31,6 @@
 import com.android.systemui.qs.TouchAnimator.Listener;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.Utils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -66,6 +66,7 @@
     private TouchAnimator mNonfirstPageAnimator;
     private TouchAnimator mNonfirstPageDelayedAnimator;
     private TouchAnimator mBrightnessAnimator;
+    private boolean mNeedsAnimatorUpdate = false;
 
     private boolean mOnKeyguard;
 
@@ -98,6 +99,12 @@
         updateAnimators();
     }
 
+
+    public void onQsScrollingChanged() {
+        // Lazily update animators whenever the scrolling changes
+        mNeedsAnimatorUpdate = true;
+    }
+
     public void setOnKeyguard(boolean onKeyguard) {
         mOnKeyguard = onKeyguard;
         updateQQSVisibility();
@@ -172,6 +179,7 @@
     }
 
     private void updateAnimators() {
+        mNeedsAnimatorUpdate = false;
         TouchAnimator.Builder firstPageBuilder = new Builder();
         TouchAnimator.Builder translationXBuilder = new Builder();
         TouchAnimator.Builder translationYBuilder = new Builder();
@@ -286,13 +294,16 @@
                     .setListener(this)
                     .build();
             // Fade in the tiles/labels as we reach the final position.
-            mFirstPageDelayedAnimator = new TouchAnimator.Builder()
+            Builder builder = new Builder()
                     .setStartDelay(EXPANDED_TILE_DELAY)
-                    .addFloat(tileLayout, "alpha", 0, 1)
-                    .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
-                    .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
-            mAllViews.add(mQsPanel.getDivider());
-            mAllViews.add(mQsPanel.getFooter().getView());
+                    .addFloat(tileLayout, "alpha", 0, 1);
+            if (mQsPanel.getSecurityFooter() != null) {
+                builder.addFloat(mQsPanel.getSecurityFooter().getView(), "alpha", 0, 1);
+            }
+            mFirstPageDelayedAnimator = builder.build();
+            if (mQsPanel.getSecurityFooter() != null) {
+                mAllViews.add(mQsPanel.getSecurityFooter().getView());
+            }
             float px = 0;
             float py = 1;
             if (tiles.size() <= 3) {
@@ -308,7 +319,6 @@
         }
         mNonfirstPageAnimator = new TouchAnimator.Builder()
                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
-                .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
                 .setListener(mNonFirstPageListener)
                 .setEndDelay(.5f)
                 .build();
@@ -339,10 +349,18 @@
             loc1[0] += view.getLeft();
             loc1[1] += view.getTop();
         }
+        if (!(view instanceof PagedTileLayout)) {
+            // Remove the scrolling position of all scroll views other than the viewpager
+            loc1[0] -= view.getScrollX();
+            loc1[1] -= view.getScrollY();
+        }
         getRelativePositionInt(loc1, (View) view.getParent(), parent);
     }
 
     public void setPosition(float position) {
+        if (mNeedsAnimatorUpdate) {
+            updateAnimators();
+        }
         if (mFirstPageAnimator == null) return;
         if (mOnKeyguard) {
             if (mShowCollapsedOnKeyguard) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1c3b685..0332bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -43,6 +43,7 @@
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
     private View mDragHandle;
+    private View mQSPanelContainer;
 
     private View mBackground;
     private View mBackgroundGradient;
@@ -61,6 +62,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQSPanel = findViewById(R.id.quick_settings_panel);
+        mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
         mQSDetail = findViewById(R.id.qs_detail);
         mHeader = findViewById(R.id.header);
         mQSCustomizer = findViewById(R.id.qs_customize);
@@ -95,7 +97,7 @@
         Configuration config = getResources().getConfiguration();
         boolean navBelow = config.smallestScreenWidthDp >= 600
                 || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
-        MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanel.getLayoutParams();
+        MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
 
         // The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to
         // subtract its height. We do not care if the collapsed notifications fit in the screen.
@@ -109,12 +111,11 @@
                 + layoutParams.rightMargin;
         final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
                 layoutParams.width);
-        // Measure with EXACTLY. That way, PagedTileLayout will only use excess height and will be
-        // measured last, after other views and padding is accounted for.
-        mQSPanel.measure(qsPanelWidthSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.EXACTLY));
-        int width = mQSPanel.getMeasuredWidth() + padding;
+        mQSPanelContainer.measure(qsPanelWidthSpec,
+                MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+        int width = mQSPanelContainer.getMeasuredWidth() + padding;
         int height = layoutParams.topMargin + layoutParams.bottomMargin
-                + mQSPanel.getMeasuredHeight() + getPaddingBottom();
+                + mQSPanelContainer.getMeasuredHeight() + getPaddingBottom();
         super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
         // QSCustomizer will always be the height of the screen, but do this after
@@ -130,7 +131,7 @@
         // Do not measure QSPanel again when doing super.onMeasure.
         // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect)
         // size to the one used for determining the number of rows and then the number of pages.
-        if (child != mQSPanel) {
+        if (child != mQSPanelContainer) {
             super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
                     parentHeightMeasureSpec, heightUsed);
         }
@@ -151,10 +152,10 @@
     }
 
     private void updateResources() {
-        LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
+        LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams();
         layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.quick_qs_offset_height);
-        mQSPanel.setLayoutParams(layoutParams);
+        mQSPanelContainer.setLayoutParams(layoutParams);
 
         mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
         mContentPaddingStart = getResources().getDimensionPixelSize(
@@ -185,7 +186,7 @@
         mQSDetail.setBottom(getTop() + height);
         // Pin the drag handle to the bottom of the panel.
         mDragHandle.setTranslationY(height - mDragHandle.getHeight());
-        mBackground.setTop(mQSPanel.getTop());
+        mBackground.setTop(mQSPanelContainer.getTop());
         mBackground.setBottom(height);
     }
 
@@ -223,7 +224,7 @@
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
             lp.rightMargin = mSideMargins;
             lp.leftMargin = mSideMargins;
-            if (view == mQSPanel) {
+            if (view == mQSPanelContainer) {
                 // QS panel lays out some of its content full width
                 mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd);
             } else if (view == mHeader) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 6af9e1e..f1bb899 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -24,7 +24,6 @@
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -72,6 +71,7 @@
     protected QuickStatusBarHeader mHeader;
     private QSCustomizer mQSCustomizer;
     protected QSPanel mQSPanel;
+    protected NonInterceptingScrollView mQSPanelScrollView;
     private QSDetail mQSDetail;
     private boolean mListening;
     private QSContainerImpl mContainer;
@@ -122,8 +122,20 @@
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         mQSPanel = view.findViewById(R.id.quick_settings_panel);
+        mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
+        mQSPanelScrollView.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    updateQsBounds();
+                });
+        mQSPanelScrollView.setOnScrollChangeListener(
+                (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            // Lazily update animators whenever the scrolling changes
+            mQSAnimator.onQsScrollingChanged();
+            mHeader.setExpandedScrollAmount(scrollY);
+        });
         mQSDetail = view.findViewById(R.id.qs_detail);
         mHeader = view.findViewById(R.id.header);
+        mQSPanel.setHeaderContainer(view.findViewById(R.id.header_text_container));
         mFooter = view.findViewById(R.id.qs_footer);
         mContainer = view.findViewById(id.quick_settings_container);
 
@@ -133,8 +145,8 @@
 
 
         mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
-        mQSAnimator = new QSAnimator(this,
-                mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
+        mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
+
 
         mQSCustomizer = view.findViewById(R.id.qs_customize);
         mQSCustomizer.setQs(this);
@@ -319,11 +331,6 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        return isCustomizing();
-    }
-
-    @Override
     public void setHeaderClickable(boolean clickable) {
         if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
     }
@@ -394,7 +401,9 @@
         mLastViewHeight = currentHeight;
 
         boolean fullyExpanded = expansion == 1;
-        int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom();
+        boolean fullyCollapsed = expansion == 0.0f;
+        int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom()
+                + mHeader.getPaddingBottom();
         float panelTranslationY = translationScaleY * heightDiff;
 
         // Let the views animate their contents correctly by giving them the necessary context.
@@ -403,19 +412,19 @@
         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
         mQSPanel.getQsTileRevealController().setExpansion(expansion);
         mQSPanel.getTileLayout().setExpansion(expansion);
-        mQSPanel.setTranslationY(translationScaleY * heightDiff);
+        mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff);
+        if (fullyCollapsed) {
+            mQSPanelScrollView.setScrollY(0);
+        }
         mQSDetail.setFullyExpanded(fullyExpanded);
 
-        if (fullyExpanded) {
-            // Always draw within the bounds of the view when fully expanded.
-            mQSPanel.setClipBounds(null);
-        } else {
+        if (!fullyExpanded) {
             // Set bounds on the QS panel so it doesn't run over the header when animating.
-            mQsBounds.top = (int) -mQSPanel.getTranslationY();
-            mQsBounds.right = mQSPanel.getWidth();
-            mQsBounds.bottom = mQSPanel.getHeight();
-            mQSPanel.setClipBounds(mQsBounds);
+            mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY();
+            mQsBounds.right = mQSPanelScrollView.getWidth();
+            mQsBounds.bottom = mQSPanelScrollView.getHeight();
         }
+        updateQsBounds();
 
         if (mQSAnimator != null) {
             mQSAnimator.setPosition(expansion);
@@ -423,31 +432,63 @@
         updateMediaPositions();
     }
 
+    private void updateQsBounds() {
+        if (mLastQSExpansion == 1.0f) {
+            // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
+            // it's a scrollview and otherwise wouldn't be clipped.
+            mQsBounds.set(0, 0, mQSPanelScrollView.getWidth(), mQSPanelScrollView.getHeight());
+        }
+        mQSPanelScrollView.setClipBounds(mQsBounds);
+    }
+
     private void updateMediaPositions() {
         if (Utils.useQsMediaPlayer(getContext())) {
             mContainer.getLocationOnScreen(mTmpLocation);
             float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight();
-            pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost());
-            pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(),
-                    mHeader.getHeaderQsPanel().getMediaHost());
+            // The Media can be scrolled off screen by default, let's offset it
+            float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
+                    + mQSPanelScrollView.getScrollRange();
+            // The expanded media host should never move below the laid out position
+            pinToBottom(expandedMediaPosition, mQSPanel.getMediaHost(), true /* expanded */);
+            // The expanded media host should never move above the laid out position
+            pinToBottom(absoluteBottomPosition, mHeader.getHeaderQsPanel().getMediaHost(),
+                    false /* expanded */);
         }
     }
 
-    private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost) {
+    private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
         View hostView = mediaHost.getHostView();
         if (mLastQSExpansion > 0) {
-            ViewGroup.MarginLayoutParams params =
-                    (ViewGroup.MarginLayoutParams) hostView.getLayoutParams();
-            float targetPosition = absoluteBottomPosition - params.bottomMargin
+            float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
                     - hostView.getHeight();
             float currentPosition = mediaHost.getCurrentBounds().top
                     - hostView.getTranslationY();
-            hostView.setTranslationY(targetPosition - currentPosition);
+            float translationY = targetPosition - currentPosition;
+            if (expanded) {
+                // Never go below the laid out position. This is necessary since the qs panel can
+                // change in height and we don't want to ever go below it's position
+                translationY = Math.min(translationY, 0);
+            } else {
+                translationY = Math.max(translationY, 0);
+            }
+            hostView.setTranslationY(translationY);
         } else {
             hostView.setTranslationY(0);
         }
     }
 
+    private float getTotalBottomMargin(View startView) {
+        int result = 0;
+        View child = startView;
+        View parent = (View) startView.getParent();
+        while (!(parent instanceof QSContainerImpl) && parent != null) {
+            result += parent.getHeight() - child.getBottom();
+            child = parent;
+            parent = (View) parent.getParent();
+        }
+        return result;
+    }
+
     private boolean headerWillBeAnimating() {
         return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
                 && !isKeyguardShowing();
@@ -504,7 +545,8 @@
     public void notifyCustomizeChanged() {
         // The customize state changed, so our height changed.
         mContainer.updateExpansion();
-        mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
+        mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE
+                : View.INVISIBLE);
         mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
@@ -521,9 +563,9 @@
             return getView().getHeight();
         }
         if (mQSDetail.isClosingDetail()) {
-            LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
+            LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams();
             int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
-                    + mQSPanel.getMeasuredHeight();
+                    + mQSPanelScrollView.getMeasuredHeight();
             return panelHeight + getView().getPaddingBottom();
         } else {
             return getView().getMeasuredHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 7844878..ecdb2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 import static com.android.systemui.util.Utils.useQsMediaPlayer;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,8 +29,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -39,7 +39,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
+import com.android.internal.widget.RemeasuringLinearLayout;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -83,38 +83,65 @@
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
     private final BroadcastDispatcher mBroadcastDispatcher;
     protected final MediaHost mMediaHost;
+
+    /**
+     * The index where the content starts that needs to be moved between parents
+     */
+    private final int mMovableContentStartIndex;
     private String mCachedSpecs = "";
-    protected final View mBrightnessView;
+
+    @Nullable
+    protected View mBrightnessView;
+    @Nullable
+    private BrightnessController mBrightnessController;
+
     private final H mHandler = new H();
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
-    private final QSTileRevealController mQsTileRevealController;
+    private QSTileRevealController mQsTileRevealController;
     /** Whether or not the QS media player feature is enabled. */
     protected boolean mUsingMediaPlayer;
+    private int mVisualMarginStart;
+    private int mVisualMarginEnd;
 
     protected boolean mExpanded;
     protected boolean mListening;
 
     private QSDetail.Callback mCallback;
-    private BrightnessController mBrightnessController;
     private final DumpManager mDumpManager;
     private final QSLogger mQSLogger;
     protected final UiEventLogger mUiEventLogger;
     protected QSTileHost mHost;
 
-    protected QSSecurityFooter mFooter;
+    @Nullable
+    protected QSSecurityFooter mSecurityFooter;
+
+    @Nullable
+    protected View mFooter;
+
+    @Nullable
+    private ViewGroup mHeaderContainer;
     private PageIndicator mFooterPageIndicator;
     private boolean mGridContentVisible = true;
     private int mContentMarginStart;
     private int mContentMarginEnd;
     private int mVisualTilePadding;
-
-    protected QSTileLayout mTileLayout;
+    private boolean mUsingHorizontalLayout;
 
     private QSCustomizer mCustomizePanel;
     private Record mDetailRecord;
 
     private BrightnessMirrorController mBrightnessMirrorController;
-    private View mDivider;
+    private LinearLayout mHorizontalLinearLayout;
+    private LinearLayout mHorizontalContentContainer;
+
+    // Only used with media
+    private QSTileLayout mHorizontalTileLayout;
+    protected QSTileLayout mRegularTileLayout;
+    protected QSTileLayout mTileLayout;
+    private int mLastOrientation = -1;
+    private int mMediaTotalBottomMargin;
+    private int mFooterMarginStartHorizontal;
+
 
     @Inject
     public QSPanel(
@@ -128,7 +155,13 @@
     ) {
         super(context, attrs);
         mUsingMediaPlayer = useQsMediaPlayer(context);
+        mMediaTotalBottomMargin = getResources().getDimensionPixelSize(
+                R.dimen.quick_settings_bottom_margin_media);
         mMediaHost = mediaHost;
+        mMediaHost.setVisibleChangedListener((visible) -> {
+            switchTileLayout();
+            return null;
+        });
         mContext = context;
         mQSLogger = qsLogger;
         mDumpManager = dumpManager;
@@ -137,71 +170,97 @@
 
         setOrientation(VERTICAL);
 
+        addViewsAboveTiles();
+        mMovableContentStartIndex = getChildCount();
+        mRegularTileLayout = createRegularTileLayout();
+
+        if (mUsingMediaPlayer) {
+            mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
+            mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+            mHorizontalLinearLayout.setClipChildren(false);
+            mHorizontalLinearLayout.setClipToPadding(false);
+
+            mHorizontalContentContainer = new RemeasuringLinearLayout(mContext);
+            mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL);
+            mHorizontalContentContainer.setClipChildren(false);
+            mHorizontalContentContainer.setClipToPadding(false);
+
+            mHorizontalTileLayout = createHorizontalTileLayout();
+            LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
+            int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
+            lp.setMarginStart(0);
+            lp.setMarginEnd(marginSize);
+            lp.gravity = Gravity.CENTER_VERTICAL;
+            mHorizontalLinearLayout.addView(mHorizontalContentContainer, lp);
+
+            lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
+            addView(mHorizontalLinearLayout, lp);
+
+            initMediaHostState();
+        }
+        addSecurityFooter();
+        if (mRegularTileLayout instanceof PagedTileLayout) {
+            mQsTileRevealController = new QSTileRevealController(mContext, this,
+                    (PagedTileLayout) mRegularTileLayout);
+        }
+        mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), mCachedSpecs);
+        updateResources();
+    }
+
+    protected void addSecurityFooter() {
+        mSecurityFooter = new QSSecurityFooter(this, mContext);
+    }
+
+    protected void addViewsAboveTiles() {
         mBrightnessView = LayoutInflater.from(mContext).inflate(
             R.layout.quick_settings_brightness_dialog, this, false);
         addView(mBrightnessView);
-
-        mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
-                R.layout.qs_paged_tile_layout, this, false);
-        mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), mCachedSpecs);
-        mTileLayout.setListening(mListening);
-        addView((View) mTileLayout);
-
-        mQsTileRevealController = new QSTileRevealController(mContext, this,
-                (PagedTileLayout) mTileLayout);
-
-        addDivider();
-
-        mFooter = new QSSecurityFooter(this, context);
-        addView(mFooter.getView());
-
-        updateResources();
-
         mBrightnessController = new BrightnessController(getContext(),
                 findViewById(R.id.brightness_slider), mBroadcastDispatcher);
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        // Add media carousel at the end
-        if (useQsMediaPlayer(getContext())) {
-            addMediaHostView();
+    protected QSTileLayout createRegularTileLayout() {
+        if (mRegularTileLayout == null) {
+            mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
+                    R.layout.qs_paged_tile_layout, this, false);
         }
+        return mRegularTileLayout;
     }
 
-    protected void addMediaHostView() {
+
+    protected QSTileLayout createHorizontalTileLayout() {
+        return createRegularTileLayout();
+    }
+
+    protected void initMediaHostState() {
         mMediaHost.setExpansion(1.0f);
         mMediaHost.setShowsOnlyActiveMedia(false);
         mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
-        ViewGroup hostView = mMediaHost.getHostView();
-        addView(hostView);
-        int bottomPadding = getResources().getDimensionPixelSize(
-                R.dimen.quick_settings_expanded_bottom_margin);
-        MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams();
-        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
-        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
-        layoutParams.bottomMargin = bottomPadding;
-        hostView.setLayoutParams(layoutParams);
-        updateMediaHostContentMargins();
-    }
-
-    protected void addDivider() {
-        mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
-        mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
-                getColorForState(mContext, Tile.STATE_ACTIVE)));
-        addView(mDivider);
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+        if (mTileLayout instanceof PagedTileLayout) {
+            // Allow the UI to be as big as it want's to, we're in a scroll view
+            int newHeight = 10000;
+            int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+            int excessHeight = newHeight - availableHeight;
+            // Measure with EXACTLY. That way, The content will only use excess height and will
+            // be measured last, after other views and padding is accounted for. This only
+            // works because our Layouts in here remeasure themselves with the exact content
+            // height.
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
+            ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight);
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
         // We want all the logic of LinearLayout#onMeasure, and for it to assign the excess space
         // not used by the other children to PagedTileLayout. However, in this case, LinearLayout
         // assumes that PagedTileLayout would use all the excess space. This is not the case as
         // PagedTileLayout height is quantized (because it shows a certain number of rows).
         // Therefore, after everything is measured, we need to make sure that we add up the correct
         // total height
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int height = getPaddingBottom() + getPaddingTop();
         int numChildren = getChildCount();
         for (int i = 0; i < numChildren; i++) {
@@ -215,10 +274,6 @@
         setMeasuredDimension(getMeasuredWidth(), height);
     }
 
-    public View getDivider() {
-        return mDivider;
-    }
-
     public QSTileRevealController getQsTileRevealController() {
         return mQsTileRevealController;
     }
@@ -273,7 +328,7 @@
 
     @Override
     public void onTuningChanged(String key, String newValue) {
-        if (QS_SHOW_BRIGHTNESS.equals(key)) {
+        if (QS_SHOW_BRIGHTNESS.equals(key) && mBrightnessView != null) {
             updateViewVisibilityForTuningValue(mBrightnessView, newValue);
         }
     }
@@ -316,6 +371,7 @@
         updateBrightnessMirror();
     }
 
+    @Nullable
     View getBrightnessView() {
         return mBrightnessView;
     }
@@ -328,7 +384,9 @@
         mHost = host;
         mHost.addCallback(this);
         setTiles(mHost.getTiles());
-        mFooter.setHostEnvironment(host);
+        if (mSecurityFooter != null) {
+            mSecurityFooter.setHostEnvironment(host);
+        }
         mCustomizePanel = customizer;
         if (mCustomizePanel != null) {
             mCustomizePanel.setHost(mHost);
@@ -341,18 +399,18 @@
      * @param pageIndicator indicator to use for page scrolling
      */
     public void setFooterPageIndicator(PageIndicator pageIndicator) {
-        if (mTileLayout instanceof PagedTileLayout) {
+        if (mRegularTileLayout instanceof PagedTileLayout) {
             mFooterPageIndicator = pageIndicator;
             updatePageIndicator();
         }
     }
 
     private void updatePageIndicator() {
-        if (mTileLayout instanceof PagedTileLayout) {
+        if (mRegularTileLayout instanceof PagedTileLayout) {
             if (mFooterPageIndicator != null) {
                 mFooterPageIndicator.setVisibility(View.GONE);
 
-                ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator);
+                ((PagedTileLayout) mRegularTileLayout).setPageIndicator(mFooterPageIndicator);
             }
         }
     }
@@ -364,6 +422,8 @@
     public void updateResources() {
         int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
         int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
+        mFooterMarginStartHorizontal = getResources().getDimensionPixelSize(
+                R.dimen.qs_footer_horizontal_margin);
         mVisualTilePadding = (int) ((tileSize - tileBg) / 2.0f);
         updatePadding();
 
@@ -379,8 +439,15 @@
 
     protected void updatePadding() {
         final Resources res = mContext.getResources();
+        int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+        if (mUsingHorizontalLayout) {
+            // When using the horizontal layout, our space is quite constrained. We therefore
+            // reduce some of the padding on the top, which makes the brightness bar overlapp,
+            // but since that has naturally quite a bit of built in padding, that's fine.
+            padding = (int) (padding * 0.6f);
+        }
         setPaddingRelative(getPaddingStart(),
-                res.getDimensionPixelSize(R.dimen.qs_panel_padding_top),
+                padding,
                 getPaddingEnd(),
                 res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
     }
@@ -388,10 +455,165 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mFooter.onConfigurationChanged();
+        if (mSecurityFooter != null) {
+            mSecurityFooter.onConfigurationChanged();
+        }
         updateResources();
 
         updateBrightnessMirror();
+
+        if (newConfig.orientation != mLastOrientation) {
+            mLastOrientation = newConfig.orientation;
+            switchTileLayout();
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mFooter = findViewById(R.id.qs_footer);
+        switchTileLayout(true /* force */);
+    }
+
+    boolean switchTileLayout() {
+        return switchTileLayout(false /* force */);
+    }
+
+    private boolean switchTileLayout(boolean force) {
+        /** Whether or not the QuickQSPanel currently contains a media player. */
+        boolean horizontal = shouldUseHorizontalLayout();
+        if (horizontal != mUsingHorizontalLayout || force) {
+            mUsingHorizontalLayout = horizontal;
+            View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout;
+            View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout;
+            ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
+            QSTileLayout newLayout = horizontal ? mHorizontalTileLayout : mRegularTileLayout;
+            if (hiddenView != null &&
+                    (mRegularTileLayout != mHorizontalTileLayout ||
+                            hiddenView != mRegularTileLayout)) {
+                // Only hide the view if the horizontal and the regular view are different,
+                // otherwise its reattached.
+                hiddenView.setVisibility(View.GONE);
+            }
+            visibleView.setVisibility(View.VISIBLE);
+            switchAllContentToParent(newParent, newLayout);
+            reAttachMediaHost();
+            if (mTileLayout != null) {
+                mTileLayout.setListening(false);
+                for (TileRecord record : mRecords) {
+                    mTileLayout.removeTile(record);
+                    record.tile.removeCallback(record.callback);
+                }
+            }
+            mTileLayout = newLayout;
+            if (mHost != null) setTiles(mHost.getTiles());
+            newLayout.setListening(mListening);
+            if (needsDynamicRowsAndColumns()) {
+                newLayout.setMinRows(horizontal ? 2 : 1);
+                // Let's use 3 columns to match the current layout
+                newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS);
+            }
+            updateTileLayoutMargins();
+            updateFooterMargin();
+            updateMediaHostContentMargins();
+            updateHorizontalLinearLayoutMargins();
+            updatePadding();
+            return true;
+        }
+        return false;
+    }
+
+    private void updateHorizontalLinearLayoutMargins() {
+        if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
+            LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams();
+            lp.bottomMargin = mMediaTotalBottomMargin - getPaddingBottom();
+            mHorizontalLinearLayout.setLayoutParams(lp);
+        }
+    }
+
+    /**
+     * @return true if the margin bottom of the media view should be on the media host or false
+     *         if they should be on the HorizontalLinearLayout. Returning {@code false} is useful
+     *         to visually center the tiles in the Media view, which doesn't work when the
+     *         expanded panel actually scrolls.
+     */
+    protected boolean displayMediaMarginsOnMedia() {
+        return true;
+    }
+
+    protected boolean needsDynamicRowsAndColumns() {
+        return true;
+    }
+
+    private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) {
+        int index = parent == this ? mMovableContentStartIndex : 0;
+
+        // Let's first move the tileLayout to the new parent, since that should come first.
+        switchToParent((View) newLayout, parent, index);
+        index++;
+
+        if (mSecurityFooter != null) {
+            View view = mSecurityFooter.getView();
+            LinearLayout.LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+            if (mUsingHorizontalLayout && mHeaderContainer != null) {
+                // Adding the security view to the header, that enables us to avoid scrolling
+                layoutParams.width = 0;
+                layoutParams.weight = 1.6f;
+                switchToParent(view, mHeaderContainer, 1 /* always in second place */);
+            } else {
+                layoutParams.width = LayoutParams.WRAP_CONTENT;
+                layoutParams.weight = 0;
+                switchToParent(view, parent, index);
+                index++;
+            }
+            view.setLayoutParams(layoutParams);
+        }
+
+        if (mFooter != null) {
+            // Then the footer with the settings
+            switchToParent(mFooter, parent, index);
+        }
+    }
+
+    private void switchToParent(View child, ViewGroup parent, int index) {
+        ViewGroup currentParent = (ViewGroup) child.getParent();
+        if (currentParent != parent || currentParent.indexOfChild(child) != index) {
+            if (currentParent != null) {
+                currentParent.removeView(child);
+            }
+            parent.addView(child, index);
+        }
+    }
+
+    private boolean shouldUseHorizontalLayout() {
+        return mUsingMediaPlayer && mMediaHost.getVisible()
+                && getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
+    }
+
+    protected void reAttachMediaHost() {
+        if (!mUsingMediaPlayer) {
+            return;
+        }
+        boolean horizontal = shouldUseHorizontalLayout();
+        ViewGroup host = mMediaHost.getHostView();
+        ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
+        ViewGroup currentParent = (ViewGroup) host.getParent();
+        if (currentParent != newParent) {
+            if (currentParent != null) {
+                currentParent.removeView(host);
+            }
+            newParent.addView(host);
+            LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams();
+            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.weight = horizontal ? 1.2f : 0;
+            // Add any bottom margin, such that the total spacing is correct. This is only
+            // necessary if the view isn't horizontal, since otherwise the padding is
+            // carried in the parent of this view (to ensure correct vertical alignment)
+            layoutParams.bottomMargin = !horizontal || displayMediaMarginsOnMedia()
+                    ? mMediaTotalBottomMargin - getPaddingBottom() : 0;
+        }
     }
 
     public void updateBrightnessMirror() {
@@ -457,13 +679,18 @@
 
     public void setListening(boolean listening, boolean expanded) {
         setListening(listening && expanded);
-        getFooter().setListening(listening);
+        if (mSecurityFooter != null) {
+            mSecurityFooter.setListening(listening);
+        }
         // Set the listening as soon as the QS fragment starts listening regardless of the expansion,
         // so it will update the current brightness before the slider is visible.
         setBrightnessListening(listening);
     }
 
     public void setBrightnessListening(boolean listening) {
+        if (mBrightnessController == null) {
+            return;
+        }
         if (listening) {
             mBrightnessController.registerCallbacks();
         } else {
@@ -472,11 +699,15 @@
     }
 
     public void refreshAllTiles() {
-        mBrightnessController.checkRestrictionAndSetEnabled();
+        if (mBrightnessController != null) {
+            mBrightnessController.checkRestrictionAndSetEnabled();
+        }
         for (TileRecord r : mRecords) {
             r.tile.refreshState();
         }
-        mFooter.refreshState();
+        if (mSecurityFooter != null) {
+            mSecurityFooter.refreshState();
+        }
     }
 
     public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
@@ -728,12 +959,15 @@
         return null;
     }
 
-    public QSSecurityFooter getFooter() {
-        return mFooter;
+    @Nullable
+    public QSSecurityFooter getSecurityFooter() {
+        return mSecurityFooter;
     }
 
     public void showDeviceMonitoringDialog() {
-        mFooter.showDeviceMonitoringDialog();
+        if (mSecurityFooter != null) {
+            mSecurityFooter.showDeviceMonitoringDialog();
+        }
     }
 
     public void setContentMargins(int startMargin, int endMargin) {
@@ -744,6 +978,24 @@
         updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding,
                 mContentMarginEnd - mVisualTilePadding);
         updateMediaHostContentMargins();
+        updateFooterMargin();
+    }
+
+    private void updateFooterMargin() {
+        if (mFooter != null) {
+            int footerMargin = 0;
+            int indicatorMargin = 0;
+            if (mUsingHorizontalLayout) {
+                footerMargin = mFooterMarginStartHorizontal;
+                indicatorMargin = footerMargin - mVisualMarginEnd;
+            }
+            updateMargins(mFooter, footerMargin, 0);
+            // The page indicator isn't centered anymore because of the visual positioning.
+            // Let's fix it by adding some margin
+            if (mFooterPageIndicator != null) {
+                updateMargins(mFooterPageIndicator, 0, indicatorMargin);
+            }
+        }
     }
 
     /**
@@ -754,16 +1006,30 @@
      * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets
      *                        to the tile. This can be set on a tileLayout
      */
-    protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) {
-        updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd);
+    private void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) {
+        mVisualMarginStart = visualMarginStart;
+        mVisualMarginEnd = visualMarginEnd;
+        updateTileLayoutMargins();
+    }
+
+    private void updateTileLayoutMargins() {
+        int marginEnd = mVisualMarginEnd;
+        if (mUsingHorizontalLayout) {
+            marginEnd = 0;
+        }
+        updateMargins((View) mTileLayout, mVisualMarginStart, marginEnd);
     }
 
     /**
      * Update the margins of the media hosts
      */
     protected void updateMediaHostContentMargins() {
-        if (mUsingMediaPlayer && mMediaHost != null) {
-            updateMargins(mMediaHost.getHostView(), mContentMarginStart, mContentMarginEnd);
+        if (mUsingMediaPlayer) {
+            int marginStart = mContentMarginStart;
+            if (mUsingHorizontalLayout) {
+                marginStart = 0;
+            }
+            updateMargins(mMediaHost.getHostView(), marginStart, mContentMarginEnd);
         }
     }
 
@@ -785,6 +1051,13 @@
         return mMediaHost;
     }
 
+    /**
+     * Set the header container of quick settings.
+     */
+    public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
+        mHeaderContainer = headerContainer;
+    }
+
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
@@ -812,6 +1085,7 @@
         }
     }
 
+
     protected static class Record {
         DetailAdapter detailAdapter;
         int x;
@@ -841,6 +1115,26 @@
 
         void setListening(boolean listening);
 
+        /**
+         * Set the minimum number of rows to show
+         *
+         * @param minRows the minimum.
+         */
+        default boolean setMinRows(int minRows) {
+            return false;
+        }
+
+        /**
+         * Set the max number of collums to show
+         *
+         * @param maxColumns the maximum
+         *
+         * @return true if the number of visible columns has changed.
+         */
+        default boolean setMaxColumns(int maxColumns) {
+            return false;
+        }
+
         default void setExpansion(float expansion) {}
 
         int getNumVisibleTiles();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 476af20..7bcaa72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -51,6 +51,7 @@
 public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener {
     protected static final String TAG = "QSSecurityFooter";
     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG_FORCE_VISIBLE = false;
 
     private final View mRootView;
     private final TextView mFooterText;
@@ -60,7 +61,6 @@
     private final SecurityController mSecurityController;
     private final ActivityStarter mActivityStarter;
     private final Handler mMainHandler;
-    private final View mDivider;
 
     private final UserManager mUm;
 
@@ -85,7 +85,6 @@
         mActivityStarter = Dependency.get(ActivityStarter.class);
         mSecurityController = Dependency.get(SecurityController.class);
         mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
-        mDivider = qsPanel == null ? null : qsPanel.getDivider();
         mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
     }
 
@@ -177,7 +176,7 @@
             boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
             String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
             CharSequence workProfileName) {
-        if (isDeviceManaged) {
+        if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
             if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
                 if (organizationName == null) {
                     return mContext.getString(
@@ -451,8 +450,7 @@
             if (mFooterTextContent != null) {
                 mFooterText.setText(mFooterTextContent);
             }
-            mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
-            if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE);
+            mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 94b4cee..affb7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -24,7 +24,6 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.UiEventLogger;
@@ -61,15 +60,7 @@
     private boolean mDisabledByPolicy;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
-    /** Whether or not the QuickQSPanel currently contains a media player. */
-    private boolean mShowHorizontalTileLayout;
-    private LinearLayout mHorizontalLinearLayout;
 
-    // Only used with media
-    private QSTileLayout mHorizontalTileLayout;
-    private QSTileLayout mRegularTileLayout;
-    private int mLastOrientation = -1;
-    private int mMediaBottomMargin;
 
     @Inject
     public QuickQSPanel(
@@ -82,59 +73,8 @@
             UiEventLogger uiEventLogger
     ) {
         super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger);
-        if (mFooter != null) {
-            removeView(mFooter.getView());
-        }
-        if (mTileLayout != null) {
-            for (int i = 0; i < mRecords.size(); i++) {
-                mTileLayout.removeTile(mRecords.get(i));
-            }
-            removeView((View) mTileLayout);
-        }
-        mMediaBottomMargin = getResources().getDimensionPixelSize(
-                R.dimen.quick_settings_media_extra_bottom_margin);
-        if (mUsingMediaPlayer) {
-            mHorizontalLinearLayout = new LinearLayout(mContext);
-            mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
-            mHorizontalLinearLayout.setClipChildren(false);
-            mHorizontalLinearLayout.setClipToPadding(false);
-
-            DoubleLineTileLayout horizontalTileLayout = new DoubleLineTileLayout(context,
-                    mUiEventLogger);
-            horizontalTileLayout.setPaddingRelative(
-                    horizontalTileLayout.getPaddingStart(),
-                    horizontalTileLayout.getPaddingTop(),
-                    horizontalTileLayout.getPaddingEnd(),
-                    mContext.getResources().getDimensionPixelSize(
-                            R.dimen.qqs_horizonal_tile_padding_bottom));
-            mHorizontalTileLayout = horizontalTileLayout;
-            mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger);
-            LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
-            int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
-            lp.setMarginStart(0);
-            lp.setMarginEnd(marginSize);
-            lp.gravity = Gravity.CENTER_VERTICAL;
-            mHorizontalLinearLayout.addView((View) mHorizontalTileLayout, lp);
-
-            sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
-
-            boolean useHorizontal = shouldUseHorizontalTileLayout();
-            mTileLayout = useHorizontal ? mHorizontalTileLayout : mRegularTileLayout;
-            mTileLayout.setListening(mListening);
-            addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */);
-            ((View) mRegularTileLayout).setVisibility(!useHorizontal ? View.VISIBLE : View.GONE);
-            mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE);
-            addView((View) mRegularTileLayout, 0);
-            super.setPadding(0, 0, 0, 0);
-            applyBottomMargin((View) mRegularTileLayout);
-        } else {
-            sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
-            mTileLayout = new HeaderTileLayout(context, mUiEventLogger);
-            mTileLayout.setListening(mListening);
-            addView((View) mTileLayout, 0 /* Between brightness and footer */);
-            super.setPadding(0, 0, 0, 0);
-            applyBottomMargin((View) mTileLayout);
-        }
+        sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+        applyBottomMargin((View) mRegularTileLayout);
     }
 
     private void applyBottomMargin(View view) {
@@ -144,48 +84,42 @@
         view.setLayoutParams(layoutParams);
     }
 
-    private void reAttachMediaHost() {
-        if (mMediaHost == null) {
-            return;
-        }
-        boolean horizontal = shouldUseHorizontalTileLayout();
-        ViewGroup host = mMediaHost.getHostView();
-        ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
-        ViewGroup currentParent = (ViewGroup) host.getParent();
-        if (currentParent != newParent) {
-            if (currentParent != null) {
-                currentParent.removeView(host);
-            }
-            newParent.addView(host);
-            LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams();
-            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
-            layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
-            layoutParams.weight = horizontal ? 1.5f : 0;
-            layoutParams.bottomMargin = mMediaBottomMargin;
-        }
+    @Override
+    protected void addSecurityFooter() {
+        // No footer needed
     }
 
     @Override
-    protected void addMediaHostView() {
-        mMediaHost.setVisibleChangedListener((visible) -> {
-            switchTileLayout();
-            return null;
-        });
+    protected void addViewsAboveTiles() {
+        // Nothing to add above the tiles
+    }
+
+    @Override
+    protected TileLayout createRegularTileLayout() {
+        return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger);
+    }
+
+    @Override
+    protected QSTileLayout createHorizontalTileLayout() {
+        return new DoubleLineTileLayout(mContext, mUiEventLogger);
+    }
+
+    @Override
+    protected void initMediaHostState() {
         mMediaHost.setExpansion(0.0f);
         mMediaHost.setShowsOnlyActiveMedia(true);
         mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
-        reAttachMediaHost();
-        updateMediaHostContentMargins();
     }
 
     @Override
-    protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) {
-        if (mUsingMediaPlayer) {
-            updateMargins((View) mRegularTileLayout, visualMarginStart, visualMarginEnd);
-            updateMargins((View) mHorizontalTileLayout, visualMarginStart, 0);
-        } else {
-            updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd);
-        }
+    protected boolean needsDynamicRowsAndColumns() {
+        return false; // QQS always have the same layout
+    }
+
+    @Override
+    protected boolean displayMediaMarginsOnMedia() {
+        // Margins should be on the container to visually center the view
+        return false;
     }
 
     @Override
@@ -194,10 +128,6 @@
     }
 
     @Override
-    protected void addDivider() {
-    }
-
-    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         Dependency.get(TunerService.class).addTunable(mNumTiles, NUM_QUICK_TILES);
@@ -237,60 +167,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        if (newConfig.orientation != mLastOrientation) {
-            mLastOrientation = newConfig.orientation;
-            switchTileLayout();
-        }
-    }
-
-    boolean switchTileLayout() {
-        if (!mUsingMediaPlayer) return false;
-        mShowHorizontalTileLayout = shouldUseHorizontalTileLayout();
-        if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) {
-            mHorizontalLinearLayout.setVisibility(View.VISIBLE);
-            ((View) mRegularTileLayout).setVisibility(View.GONE);
-            mTileLayout.setListening(false);
-            for (TileRecord record : mRecords) {
-                mTileLayout.removeTile(record);
-                record.tile.removeCallback(record.callback);
-            }
-            mTileLayout = mHorizontalTileLayout;
-            if (mHost != null) setTiles(mHost.getTiles());
-            mTileLayout.setListening(mListening);
-            reAttachMediaHost();
-            return true;
-        } else if (!mShowHorizontalTileLayout
-                && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
-            mHorizontalLinearLayout.setVisibility(View.GONE);
-            ((View) mRegularTileLayout).setVisibility(View.VISIBLE);
-            mTileLayout.setListening(false);
-            for (TileRecord record : mRecords) {
-                mTileLayout.removeTile(record);
-                record.tile.removeCallback(record.callback);
-            }
-            mTileLayout = mRegularTileLayout;
-            if (mHost != null) setTiles(mHost.getTiles());
-            mTileLayout.setListening(mListening);
-            reAttachMediaHost();
-            return true;
-        }
-        return false;
-    }
-
-    private boolean shouldUseHorizontalTileLayout() {
-        return mMediaHost.getVisible()
-                && getResources().getConfiguration().orientation
-                        == Configuration.ORIENTATION_LANDSCAPE;
-    }
-
-    /** Returns true if this panel currently uses a horizontal tile layout. */
-    public boolean usesHorizontalLayout() {
-        return mShowHorizontalTileLayout;
-    }
-
-    @Override
     public void setHost(QSTileHost host, QSCustomizer customizer) {
         super.setHost(host, customizer);
         setTiles(mHost.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 20e47b2..311eda2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -36,13 +36,13 @@
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Pair;
 import android.view.ContextThemeWrapper;
 import android.view.DisplayCutout;
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -55,6 +55,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DualToneHandler;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
@@ -149,6 +150,8 @@
     private int mWaterfallTopInset;
     private int mCutOutPaddingLeft;
     private int mCutOutPaddingRight;
+    private float mExpandedHeaderAlpha = 1.0f;
+    private float mKeyguardExpansionFraction;
 
     @Inject
     public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -356,7 +359,7 @@
 
     private void updateHeaderTextContainerAlphaAnimator() {
         mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder()
-                .addFloat(mHeaderTextContainerView, "alpha", 0, 0, 1)
+                .addFloat(mHeaderTextContainerView, "alpha", 0, 0, mExpandedHeaderAlpha)
                 .build();
     }
 
@@ -403,6 +406,7 @@
                 updateResources();
             }
         }
+        mKeyguardExpansionFraction = keyguardExpansionFraction;
     }
 
     public void disable(int state1, int state2, boolean animate) {
@@ -596,4 +600,22 @@
         }
         updateClockPadding();
     }
+
+    public void setExpandedScrollAmount(int scrollY) {
+        // The scrolling of the expanded qs has changed. Since the header text isn't part of it,
+        // but would overlap content, we're fading it out.
+        float newAlpha = 1.0f;
+        if (mHeaderTextContainerView.getHeight() > 0) {
+            newAlpha = MathUtils.map(0, mHeaderTextContainerView.getHeight() / 2.0f, 1.0f, 0.0f,
+                    scrollY);
+            newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha);
+        }
+        mHeaderTextContainerView.setScrollY(scrollY);
+        if (newAlpha != mExpandedHeaderAlpha) {
+            mExpandedHeaderAlpha = newAlpha;
+            mHeaderTextContainerView.setAlpha(MathUtils.lerp(0.0f, mExpandedHeaderAlpha,
+                    mKeyguardExpansionFraction));
+            updateHeaderTextContainerAlphaAnimator();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 383c29d..694492a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -17,6 +17,7 @@
 
 public class TileLayout extends ViewGroup implements QSTileLayout {
 
+    public static final int NO_MAX_COLUMNS = 100;
     private static final float TILE_ASPECT = 1.2f;
 
     private static final String TAG = "TileLayout";
@@ -36,6 +37,9 @@
 
     // Prototyping with less rows
     private final boolean mLessRows;
+    private int mMinRows = 1;
+    private int mMaxColumns = NO_MAX_COLUMNS;
+    private int mResourceColumns;
 
     public TileLayout(Context context) {
         this(context, null);
@@ -64,6 +68,22 @@
         }
     }
 
+    @Override
+    public boolean setMinRows(int minRows) {
+        if (mMinRows != minRows) {
+            mMinRows = minRows;
+            updateResources();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean setMaxColumns(int maxColumns) {
+        mMaxColumns = maxColumns;
+        return updateColumns();
+    }
+
     public void addTile(TileRecord tile) {
         mRecords.add(tile);
         tile.tile.setListening(this, mListening);
@@ -91,21 +111,26 @@
 
     public boolean updateResources() {
         final Resources res = mContext.getResources();
-        final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
+        mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
         mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
         mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
         mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
         mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
         mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
-        if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1);
-        if (mColumns != columns) {
-            mColumns = columns;
+        if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1);
+        if (updateColumns()) {
             requestLayout();
             return true;
         }
         return false;
     }
 
+    private boolean updateColumns() {
+        int oldColumns = mColumns;
+        mColumns = Math.min(mResourceColumns, mMaxColumns);
+        return oldColumns != mColumns;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED
@@ -142,18 +167,19 @@
      * Determines the maximum number of rows that can be shown based on height. Clips at a minimum
      * of 1 and a maximum of mMaxAllowedRows.
      *
-     * @param heightMeasureSpec Available height.
+     * @param allowedHeight The height this view has visually available
      * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows.
      */
-    public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) {
-        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop
+    public boolean updateMaxRows(int allowedHeight, int tilesCount) {
+        final int availableHeight =  allowedHeight - mCellMarginTop
+                // Add the cell margin in order to divide easily by the height + the margin below
                 + mCellMarginVertical;
         final int previousRows = mRows;
         mRows = availableHeight / (mCellHeight + mCellMarginVertical);
-        if (mRows >= mMaxAllowedRows) {
+        if (mRows < mMinRows) {
+            mRows = mMinRows;
+        } else if (mRows >= mMaxAllowedRows) {
             mRows = mMaxAllowedRows;
-        } else if (mRows <= 1) {
-            mRows = 1;
         }
         if (mRows > (tilesCount + mColumns - 1) / mColumns) {
             mRows = (tilesCount + mColumns - 1) / mColumns;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 08be4f8..011ad19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -255,23 +255,11 @@
 
     @Throws(InflationException::class)
     private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
-        // Attempt to extract form shortcut.
-        val conversationId = entry.ranking.channel.conversationId
-        val query = LauncherApps.ShortcutQuery()
-                .setPackage(entry.sbn.packageName)
-                .setQueryFlags(
-                        LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
-                                or LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
-                .setShortcutIds(listOf(conversationId))
-        val shortcuts = launcherApps.getShortcuts(query, entry.sbn.user)
         var ic: Icon? = null
-        if (shortcuts != null && shortcuts.isNotEmpty()) {
-            ic = shortcuts[0].icon
-        }
 
-        // Fall back to notification large icon if available
-        if (ic == null) {
-            ic = entry.sbn.notification.getLargeIcon()
+        val shortcut = entry.ranking.shortcutInfo
+        if (shortcut != null) {
+            ic = launcherApps.getShortcutIcon(shortcut)
         }
 
         // Fall back to extract from message
@@ -290,6 +278,11 @@
             }
         }
 
+        // Fall back to notification large icon if available
+        if (ic == null) {
+            ic = entry.sbn.notification.getLargeIcon()
+        }
+
         // Revert to small icon if still not available
         if (ic == null) {
             ic = entry.sbn.notification.smallIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d884bdd..9e7bf62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -3071,7 +3071,7 @@
         return new TouchHandler() {
             @Override
             public boolean onInterceptTouchEvent(MotionEvent event) {
-                if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
+                if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
                     return false;
                 }
                 initDownStates(event);
@@ -3098,7 +3098,8 @@
 
             @Override
             public boolean onTouch(View v, MotionEvent event) {
-                if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
+                if (mBlockTouches || (mQsFullyExpanded && mQs != null
+                        && mQs.disallowPanelTouches())) {
                     return false;
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt
new file mode 100644
index 0000000..bab9347
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.util
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+/**
+ * Basically a normal linear layout but doesn't grow its children with weight 1 even when its
+ * measured with exactly.
+ */
+class NeverExactlyLinearLayout @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr) {
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+
+        val (widthExactly, usedWidthSpec, width) = getNonExactlyMeasureSpec(widthMeasureSpec)
+        val (heightExactly, usedHeightSpec, height) = getNonExactlyMeasureSpec(heightMeasureSpec)
+
+        super.onMeasure(usedWidthSpec, usedHeightSpec)
+        if (widthExactly || heightExactly) {
+            val newWidth = if (widthExactly) width else measuredWidth
+            val newHeight = if (heightExactly) height else measuredHeight
+            setMeasuredDimension(newWidth, newHeight)
+        }
+    }
+
+    /**
+     * Obtain a measurespec that's not exactly
+     *
+     * @return a triple, where we return 1. if this was exactly, 2. the new measurespec, 3. the size
+     *         of the measurespec
+     */
+    private fun getNonExactlyMeasureSpec(measureSpec: Int): Triple<Boolean, Int, Int> {
+        var newSpec = measureSpec
+        val isExactly = MeasureSpec.getMode(measureSpec) == MeasureSpec.EXACTLY
+        val size = MeasureSpec.getSize(measureSpec)
+        if (isExactly) {
+            newSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)
+        }
+        return Triple(isExactly, newSpec, size)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 7b11452..32e3a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -19,13 +19,11 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IDisplayWindowInsetsController;
@@ -38,7 +36,6 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
-import com.android.internal.view.IInputMethodManager;
 import com.android.systemui.TransactionPool;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -355,16 +352,6 @@
                     dispatchEndPositioning(mDisplayId, mCancelled, t);
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
                         t.hide(mImeSourceControl.getLeash());
-                        final IInputMethodManager imms = getImms();
-                        if (imms != null) {
-                            try {
-                                // Remove the IME surface to make the insets invisible for
-                                // non-client controlled insets.
-                                imms.removeImeSurface();
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "Failed to remove IME surface.", e);
-                            }
-                        }
                     }
                     t.apply();
                     mTransactionPool.release(t);
@@ -395,9 +382,9 @@
          * Called when the IME position is starting to animate.
          *
          * @param hiddenTop The y position of the top of the IME surface when it is hidden.
-         * @param shownTop  The y position of the top of the IME surface when it is shown.
-         * @param showing   {@code true} when we are animating from hidden to shown, {@code false}
-         *                  when animating from shown to hidden.
+         * @param shownTop The y position of the top of the IME surface when it is shown.
+         * @param showing {@code true} when we are animating from hidden to shown, {@code false}
+         *                            when animating from shown to hidden.
          */
         default void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                 boolean showing, SurfaceControl.Transaction t) {}
@@ -419,9 +406,4 @@
         default void onImeEndPositioning(int displayId, boolean cancel,
                 SurfaceControl.Transaction t) {}
     }
-
-    public IInputMethodManager getImms() {
-        return IInputMethodManager.Stub.asInterface(
-                ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 05b31c8..cbb0711 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.util.animation.UniqueObjectHostView;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -108,12 +109,14 @@
         mDependency.injectMockDependency(SecurityController.class);
         mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
         mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class));
+        when(mMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(getContext()));
 
         mUiEventLogger = new UiEventLoggerFake();
         mTestableLooper.runWithLooper(() -> {
             mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
             mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
                     mQSLogger, mMediaHost, mUiEventLogger);
+            mQsPanel.onFinishInflate();
             // Provides a parent with non-zero size for QSPanel
             mParentView = new FrameLayout(mContext);
             mParentView.addView(mQsPanel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
new file mode 100644
index 0000000..b63e66f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 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.notification.icon;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.Person
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.os.SystemClock
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner;
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+import org.junit.runner.RunWith;
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class IconManagerTest: SysuiTestCase() {
+    companion object {
+        private const val TEST_PACKAGE_NAME = "test";
+        private const val TEST_UID = 0;
+    }
+
+
+    private var id = 0
+    private val context = InstrumentationRegistry.getTargetContext();
+    @Mock private lateinit var shortcut: ShortcutInfo
+    @Mock private lateinit var shortcutIc: Icon
+    @Mock private lateinit var messageIc: Icon
+    @Mock private lateinit var largeIc: Icon
+    @Mock private lateinit var smallIc: Icon
+    @Mock private lateinit var drawable: Drawable
+    @Mock private lateinit var row: ExpandableNotificationRow
+
+    @Mock private lateinit var notifCollection: CommonNotifCollection
+    @Mock private lateinit var launcherApps: LauncherApps
+
+    private val iconBuilder = IconBuilder(context)
+
+    private lateinit var iconManager: IconManager
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(shortcutIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
+        `when`(messageIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
+        `when`(largeIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
+        `when`(smallIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable)
+
+        `when`(shortcut.icon).thenReturn(shortcutIc)
+        `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
+
+        iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+    }
+
+    @Test
+    fun testCreateIcons_importantConversation_shortcutIcon() {
+        val entry = notificationEntry(true, true, true)
+        entry?.channel?.isImportantConversation = true
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, shortcutIc)
+    }
+
+    @Test
+    fun testCreateIcons_importantConversation_messageIcon() {
+        val entry = notificationEntry(false, true, true)
+        entry?.channel?.isImportantConversation = true
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, messageIc)
+    }
+
+    @Test
+    fun testCreateIcons_importantConversation_largeIcon() {
+        val entry = notificationEntry(false, false, true)
+        entry?.channel?.isImportantConversation = true
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, largeIc)
+    }
+
+    @Test
+    fun testCreateIcons_importantConversation_smallIcon() {
+        val entry = notificationEntry(false, false, false)
+        entry?.channel?.isImportantConversation = true
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, smallIc)
+    }
+
+    @Test
+    fun testCreateIcons_notImportantConversation() {
+        val entry = notificationEntry(true, true, true)
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, smallIc)
+    }
+
+    @Test
+    fun testCreateIcons_sensitiveImportantConversation() {
+        val entry = notificationEntry(true, false, false)
+        entry?.setSensitive(true, true);
+        entry?.channel?.isImportantConversation = true
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, shortcutIc)
+        assertEquals(entry?.icons?.shelfIcon?.sourceIcon, smallIc)
+        assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc)
+    }
+
+    @Test
+    fun testUpdateIcons_sensitivityChange() {
+        val entry = notificationEntry(true, false, false)
+        entry?.channel?.isImportantConversation = true
+        entry?.setSensitive(true, true);
+        entry?.let {
+            iconManager.createIcons(it)
+        }
+        assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc)
+        entry?.setSensitive(false, false);
+        entry?.let {
+            iconManager.updateIcons(it)
+        }
+        assertEquals(entry?.icons?.shelfIcon?.sourceIcon, shortcutIc)
+    }
+
+    private fun notificationEntry(
+            hasShortcut: Boolean,
+            hasMessage: Boolean,
+            hasLargeIcon: Boolean
+    ): NotificationEntry? {
+        val n = Notification.Builder(mContext, "id")
+                .setSmallIcon(smallIc)
+                .setContentTitle("Title")
+                .setContentText("Text")
+
+        if (hasMessage) {
+            n.style = Notification.MessagingStyle("")
+                    .addMessage(Notification.MessagingStyle.Message(
+                            "",
+                            SystemClock.currentThreadTimeMillis(),
+                            Person.Builder().setIcon(messageIc).build()
+                    ))
+        }
+
+        if (hasLargeIcon) {
+            n.setLargeIcon(largeIc)
+        }
+
+        val builder = NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setId(id++)
+                .setNotification(n.build())
+                .setChannel(NotificationChannel("id", "", IMPORTANCE_DEFAULT))
+                .setUser(UserHandle(ActivityManager.getCurrentUser()))
+
+        if (hasShortcut) {
+            builder.setShortcutInfo(shortcut)
+        }
+
+        val entry = builder.build()
+        entry.row = row
+        entry.setSensitive(false, true);
+        return entry
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b647a1a..65a1301 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -207,7 +207,6 @@
     static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
     static final int MSG_INITIALIZE_IME = 1040;
     static final int MSG_CREATE_SESSION = 1050;
-    static final int MSG_REMOVE_IME_SURFACE = 1060;
 
     static final int MSG_START_INPUT = 2000;
 
@@ -3947,12 +3946,6 @@
         }
     }
 
-    @Override
-    public void removeImeSurface() {
-        mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
-    }
-
     @BinderThread
     private void notifyUserAction(@NonNull IBinder token) {
         if (DEBUG) {
@@ -4223,15 +4216,6 @@
                 args.recycle();
                 return true;
             }
-            case MSG_REMOVE_IME_SURFACE: {
-                try {
-                    if (mEnabledSession != null && mEnabledSession.session != null) {
-                        mEnabledSession.session.removeImeSurface();
-                    }
-                } catch (RemoteException e) {
-                }
-                return true;
-            }
             // ---------------------------------------------------------
 
             case MSG_START_INPUT: {
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index d025b0f..6c415ca 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1347,13 +1347,14 @@
                 for (WindowInfo windowInfo : clientInfo.mWindowMap.values()) {
                     if (windowInfo.mWindowHandle == targetWindowHandle) {
                         final IBinder targetWindowToken = windowInfo.mWindowToken;
-                        // TODO(yukawa): Report targetWindowToken and targetWindowToken to WMS.
                         if (DEBUG) {
                             Slog.v(TAG, "reportImeWindowTarget"
                                     + " clientId=" + clientId
                                     + " imeWindowToken=" + imeWindowToken
                                     + " targetWindowToken=" + targetWindowToken);
                         }
+                        mIWindowManagerInternal.updateInputMethodTargetWindow(
+                                imeWindowToken, targetWindowToken);
                     }
                 }
                 // not found.
@@ -1462,12 +1463,6 @@
 
         @BinderThread
         @Override
-        public void removeImeSurface() {
-            reportNotSupported();
-        }
-
-        @BinderThread
-        @Override
         public boolean showSoftInput(
                 IInputMethodClient client, IBinder token, int flags,
                 ResultReceiver resultReceiver) {
@@ -1496,6 +1491,9 @@
                     case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
                         try {
                             clientInfo.mMSInputMethodSession.showSoftInput(flags, resultReceiver);
+
+                            // Forcing WM to show IME on imeTargetWindow
+                            mWindowManagerInternal.showImePostLayout(token);
                         } catch (RemoteException e) {
                         }
                         break;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a85bd06..53aad23 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2829,14 +2829,12 @@
     /**
      * A list of 4 customized LTE Reference Signal Signal to Noise Ratio (RSSNR) thresholds.
      *
-     * 4 threshold integers must be within the boundaries [-200, 300], and the levels are:
-     *     "NONE: [-200, threshold1)"
+     * 4 threshold integers must be within the boundaries [-20 dB, 30 dB], and the levels are:
+     *     "NONE: [-20, threshold1)"
      *     "POOR: [threshold1, threshold2)"
      *     "MODERATE: [threshold2, threshold3)"
      *     "GOOD:  [threshold3, threshold4)"
-     *     "EXCELLENT:  [threshold4, 300]"
-     * Note: the unit of the values is 10*db; it is derived by multiplying 10 on the original dB
-     * value reported by modem.
+     *     "EXCELLENT:  [threshold4, 30]"
      *
      * This key is considered invalid if the format is violated. If the key is invalid or
      * not configured, a default value set will apply.
@@ -4198,10 +4196,10 @@
                 });
         sDefaults.putIntArray(KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
                 new int[] {
-                        -30, /* SIGNAL_STRENGTH_POOR */
-                        10,  /* SIGNAL_STRENGTH_MODERATE */
-                        45,  /* SIGNAL_STRENGTH_GOOD */
-                        130  /* SIGNAL_STRENGTH_GREAT */
+                        -3, /* SIGNAL_STRENGTH_POOR */
+                        1,  /* SIGNAL_STRENGTH_MODERATE */
+                        5,  /* SIGNAL_STRENGTH_GOOD */
+                        13  /* SIGNAL_STRENGTH_GREAT */
                 });
         sDefaults.putIntArray(KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
                 new int[] {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 2529387..c26936e 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -118,7 +118,7 @@
      * @param rssi in dBm [-113,-51], UNKNOWN
      * @param rsrp in dBm [-140,-43], UNKNOWN
      * @param rsrq in dB [-34, 3], UNKNOWN
-     * @param rssnr in 10*dB [-200, +300], UNKNOWN
+     * @param rssnr in dB [-20, +30], UNKNOWN
      * @param cqi [0, 15], UNKNOWN
      * @param timingAdvance [0, 1282], UNKNOWN
      *
@@ -131,7 +131,7 @@
         mSignalStrength = mRssi;
         mRsrp = inRangeOrUnavailable(rsrp, -140, -43);
         mRsrq = inRangeOrUnavailable(rsrq, -34, 3);
-        mRssnr = inRangeOrUnavailable(rssnr, -200, 300);
+        mRssnr = inRangeOrUnavailable(rssnr, -20, 30);
         mCqi = inRangeOrUnavailable(cqi, 0, 15);
         mTimingAdvance = inRangeOrUnavailable(timingAdvance, 0, 1282);
         updateLevel(null, null);
@@ -143,7 +143,7 @@
         this(convertRssiAsuToDBm(lte.signalStrength),
                 lte.rsrp != CellInfo.UNAVAILABLE ? -lte.rsrp : lte.rsrp,
                 lte.rsrq != CellInfo.UNAVAILABLE ? -lte.rsrq : lte.rsrq,
-                lte.rssnr, lte.cqi, lte.timingAdvance);
+                convertRssnrUnitFromTenDbToDB(lte.rssnr), lte.cqi, lte.timingAdvance);
     }
 
     /** @hide */
@@ -208,10 +208,10 @@
     };
     // Lifted from Default carrier configs and max range of RSSNR
     private static final int[] sRssnrThresholds = new int[] {
-            -30, /* SIGNAL_STRENGTH_POOR */
-            10,  /* SIGNAL_STRENGTH_MODERATE */
-            45,  /* SIGNAL_STRENGTH_GOOD */
-            130  /* SIGNAL_STRENGTH_GREAT */
+            -3, /* SIGNAL_STRENGTH_POOR */
+            1,  /* SIGNAL_STRENGTH_MODERATE */
+            5,  /* SIGNAL_STRENGTH_GOOD */
+            13  /* SIGNAL_STRENGTH_GREAT */
     };
     private static final int sRsrpBoost = 0;
 
@@ -556,6 +556,10 @@
         Rlog.w(LOG_TAG, s);
     }
 
+    private static int convertRssnrUnitFromTenDbToDB(int rssnr) {
+        return rssnr / 10;
+    }
+
     private static int convertRssiAsuToDBm(int rssiAsu) {
         if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN) {
             return CellInfo.UNAVAILABLE;
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
index 4273f5a..af62ba4 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -19,8 +19,8 @@
 import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
-
 import android.telephony.Annotation.NetworkType;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
@@ -241,13 +241,13 @@
                 .append(",mCellBandwidthDownlinkKhz=")
                 .append(mCellBandwidthDownlinkKhz)
                 .append(",mRat=")
-                .append(mRat)
+                .append(TelephonyManager.getNetworkTypeName(mRat))
                 .append(",mFrequencyRange=")
-                .append(mFrequencyRange)
+                .append(ServiceState.frequencyRangeToString(mFrequencyRange))
                 .append(",mChannelNumber=")
                 .append(mChannelNumber)
                 .append(",mContextIds=")
-                .append(mContextIds.toString())
+                .append(Arrays.toString(mContextIds))
                 .append(",mPhysicalCellId=")
                 .append(mPhysicalCellId)
                 .append("}")
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index c6b06b4..9e2ba68 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1033,6 +1033,26 @@
     }
 
     /**
+     * Convert frequency range into string
+     *
+     * @param range The cellular frequency range
+     * @return Frequency range in string format
+     *
+     * @hide
+     */
+    public static @NonNull String frequencyRangeToString(@FrequencyRange int range) {
+        switch (range) {
+            case FREQUENCY_RANGE_UNKNOWN: return "UNKNOWN";
+            case FREQUENCY_RANGE_LOW: return "LOW";
+            case FREQUENCY_RANGE_MID: return "MID";
+            case FREQUENCY_RANGE_HIGH: return "HIGH";
+            case FREQUENCY_RANGE_MMWAVE: return "MMWAVE";
+            default:
+                return Integer.toString(range);
+        }
+    }
+
+    /**
      * Convert RIL Service State to String
      *
      * @param serviceState
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fadebaa..ee14608 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8197,6 +8197,140 @@
         return false;
     }
 
+    /** @hide */
+    @IntDef({
+            ALLOWED_NETWORK_TYPES_REASON_POWER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AllowedNetworkTypesReason{}
+
+    /**
+     * To indicate allowed network type change is requested by power manager.
+     * Power Manger configuration won't affect the settings configured through
+     * {@link setAllowedNetworkTypes} and will result in allowing network types that are in both
+     * configurations (i.e intersection of both sets).
+     * @hide
+     */
+    public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 0;
+
+    /**
+     * Set the allowed network types of the device and
+     * provide the reason triggering the allowed network change.
+     * This can be called for following reasons
+     * <ol>
+     * <li>Allowed network types control by power manager
+     * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER}
+     * </ol>
+     * This API will result in allowing an intersection of allowed network types for all reasons,
+     * including the configuration done through {@link setAllowedNetworkTypes}.
+     * While this API and {@link setAllowedNetworkTypes} is controlling allowed network types
+     * on device, user preference will still be set through {@link #setPreferredNetworkTypeBitmask}.
+     * Thus resultant network type configured on modem will be an intersection of the network types
+     * from setAllowedNetworkTypesForReason, {@link setAllowedNetworkTypes}
+     * and {@link #setPreferredNetworkTypeBitmask}.
+     *
+     * @param reason the reason the allowed network type change is taking place
+     * @param allowedNetworkTypes The bitmask of allowed network types.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setAllowedNetworkTypesForReason(@AllowedNetworkTypesReason int reason,
+            @NetworkTypeBitMask long allowedNetworkTypes) {
+        if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) {
+            throw new IllegalArgumentException("invalid AllowedNetworkTypesReason.");
+        }
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setAllowedNetworkTypesForReason(getSubId(), reason,
+                        allowedNetworkTypes);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setAllowedNetworkTypesForReason RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the allowed network types for certain reason.
+     *
+     * {@link #getAllowedNetworkTypesForReason} returns allowed network type for a
+     * specific reason. For effective allowed network types configured on device,
+     * query {@link getEffectiveAllowedNetworkTypes}
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *s
+     * @param reason the reason the allowed network type change is taking place
+     * @return the allowed network type bitmask
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @NetworkTypeBitMask long getAllowedNetworkTypesForReason(
+            @AllowedNetworkTypesReason int reason) {
+        if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) {
+            throw new IllegalArgumentException("invalid AllowedNetworkTypesReason.");
+        }
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getAllowedNetworkTypesForReason(getSubId(), reason);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getAllowedNetworkTypesForReason RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return -1;
+    }
+
+    /**
+     * Get bit mask of all network types.
+     *
+     * @return bit mask of all network types
+     * @hide
+     */
+    public static @NetworkTypeBitMask long getAllNetworkTypesBitmask() {
+        return NETWORK_STANDARDS_FAMILY_BITMASK_3GPP | NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2;
+    }
+
+    /**
+     * Get the allowed network types configured on the device.
+     * This API will return an intersection of allowed network types for all reasons,
+     * including the configuration done through setAllowedNetworkTypes
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @return the allowed network type bitmask
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @NetworkTypeBitMask long getEffectiveAllowedNetworkTypes() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getEffectiveAllowedNetworkTypes(getSubId());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getEffectiveAllowedNetworkTypes RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return -1;
+    }
+
     /**
      * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
      *
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3690200..b70937c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -956,6 +956,35 @@
     boolean setAllowedNetworkTypes(int subId, long allowedNetworkTypes);
 
     /**
+     * Get the allowed network types for certain reason.
+     *
+     * @param subId the id of the subscription.
+     * @param reason the reason the allowed network type change is taking place
+     * @return allowedNetworkTypes the allowed network types.
+     */
+    long getAllowedNetworkTypesForReason(int subId, int reason);
+
+    /**
+     * Get the effective allowed network types on the device. This API will
+     * return an intersection of allowed network types for all reasons,
+     * including the configuration done through setAllowedNetworkTypes
+     *
+     * @param subId the id of the subscription.
+     * @return allowedNetworkTypes the allowed network types.
+     */
+     long getEffectiveAllowedNetworkTypes(int subId);
+
+    /**
+     * Set the allowed network types and provide the reason triggering the allowed network change.
+     *
+     * @param subId the id of the subscription.
+     * @param reason the reason the allowed network type change is taking place
+     * @param allowedNetworkTypes the allowed network types.
+     * @return true on success; false on any failure.
+     */
+    boolean setAllowedNetworkTypesForReason(int subId, int reason, long allowedNetworkTypes);
+
+    /**
      * Set the preferred network type.
      * Used for device configuration by some CDMA operators.
      *