Merge changes Id9289826,If9cc89df

* changes:
  Put security footers in new footer
  Implement correct dimens for new footer
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
new file mode 100644
index 0000000..5343411
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="@dimen/qs_security_footer_single_line_height"
+    android:layout_weight="1"
+    android:gravity="center"
+    android:clickable="true"
+    android:visibility="gone">
+
+    <LinearLayout
+        android:id="@+id/fgs_text_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
+        android:background="@drawable/qs_security_footer_background"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:paddingHorizontal="@dimen/qs_footer_padding"
+        >
+
+        <ImageView
+            android:id="@+id/primary_footer_icon"
+            android:layout_width="@dimen/qs_footer_icon_size"
+            android:layout_height="@dimen/qs_footer_icon_size"
+            android:gravity="start"
+            android:layout_marginEnd="12dp"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_info_outline"
+            android:tint="?android:attr/textColorSecondary" />
+
+        <TextView
+            android:id="@+id/footer_text"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:maxLines="1"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
+            android:textColor="?android:attr/textColorSecondary"/>
+
+        <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="@*android:drawable/ic_chevron_end"
+            android:autoMirrored="true"
+            android:tint="?android:attr/textColorSecondary" />
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/fgs_number_container"
+        android:layout_width="@dimen/qs_footer_action_button_size"
+        android:layout_height="@dimen/qs_footer_action_button_size"
+        android:background="@drawable/qs_footer_action_circle"
+        android:focusable="true"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/fgs_number"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
+            android:layout_gravity="center"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="18sp"/>
+        <ImageView
+            android:id="@+id/fgs_new"
+            android:layout_width="12dp"
+            android:layout_height="12dp"
+            android:scaleType="fitCenter"
+            android:layout_gravity="bottom|end"
+            android:src="@drawable/new_fgs_dot"
+            android:contentDescription="@string/fgs_dot_content_description"
+            />
+    </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
index 95bdd89..4884df7 100644
--- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
@@ -20,34 +20,41 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_footer_height"
+    android:layout_height="@dimen/new_footer_height"
+    android:elevation="@dimen/qs_panel_elevation"
+    android:paddingTop="8dp"
+    android:paddingBottom="4dp"
+    android:background="@drawable/qs_footer_actions_background"
     android:gravity="center_vertical"
     android:layout_gravity="bottom"
 >
 
-    <View
-        android:layout_height="1dp"
+    <LinearLayout
+        android:id="@+id/security_footers_container"
+        android:orientation="horizontal"
+        android:layout_height="@dimen/qs_footer_action_button_size"
         android:layout_width="0dp"
         android:layout_weight="1"
-        />
+    />
 
+    <!-- Negative margin equal to -->
     <LinearLayout
         android:layout_height="match_parent"
         android:layout_width="wrap_content"
+        android:layout_marginEnd="@dimen/new_qs_footer_action_inset_negative"
         >
 
         <com.android.systemui.statusbar.phone.MultiUserSwitch
             android:id="@+id/multi_user_switch"
             android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
-            android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
             android:background="@drawable/qs_footer_action_circle"
             android:focusable="true">
 
             <ImageView
                 android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_expanded_size"
-                android:layout_height="@dimen/multi_user_avatar_expanded_size"
+                android:layout_width="@dimen/qs_footer_icon_size"
+                android:layout_height="@dimen/qs_footer_icon_size"
                 android:layout_gravity="center"
                 android:scaleType="centerInside" />
         </com.android.systemui.statusbar.phone.MultiUserSwitch>
@@ -56,19 +63,17 @@
             android:id="@+id/settings_button_container"
             android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
-            android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
             android:background="@drawable/qs_footer_action_circle"
             android:clipChildren="false"
             android:clipToPadding="false">
 
             <com.android.systemui.statusbar.phone.SettingsButton
                 android:id="@+id/settings_button"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/qs_footer_action_button_size"
+                android:layout_width="@dimen/qs_footer_icon_size"
+                android:layout_height="@dimen/qs_footer_icon_size"
                 android:layout_gravity="center"
                 android:background="@android:color/transparent"
                 android:contentDescription="@string/accessibility_quick_settings_settings"
-                android:padding="@dimen/qs_footer_icon_padding"
                 android:scaleType="centerInside"
                 android:src="@drawable/ic_settings"
                 android:tint="?android:attr/textColorPrimary" />
diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/new_fgs_dot.xml
new file mode 100644
index 0000000..759ddaf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/new_fgs_dot.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2022, 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval"
+    android:width="12dp"
+    android:height="12dp">
+    <solid android:color="@*android:color/red" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index f54c30f..d057f5f 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -15,10 +15,7 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset"
-    android:insetLeft="@dimen/qs_footer_action_inset"
-    android:insetRight="@dimen/qs_footer_action_inset">
+       android:inset="@dimen/new_qs_footer_action_inset">
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 1a323bc..944061c 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -15,10 +15,7 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset"
-    android:insetLeft="@dimen/qs_footer_action_inset"
-    android:insetRight="@dimen/qs_footer_action_inset">
+    android:inset="@dimen/new_qs_footer_action_inset">
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
new file mode 100644
index 0000000..c9517cd9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+    <shape>
+        <solid android:color="?attr/underSurfaceColor"/>
+        <corners android:topLeftRadius="@dimen/qs_corner_radius"
+                 android:topRightRadius="@dimen/qs_corner_radius"/>
+    </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
index 860d23b..381af50 100644
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
@@ -15,8 +15,8 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_security_footer_background_inset"
-    android:insetBottom="@dimen/qs_security_footer_background_inset"
+    android:insetTop="@dimen/qs_footer_action_inset"
+    android:insetBottom="@dimen/qs_footer_action_inset"
     >
     <ripple
         android:color="?android:attr/colorControlHighlight">
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 22abd0c..85b33cc 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -25,7 +25,7 @@
         android:id="@+id/expanded_qs_scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:elevation="4dp"
+        android:elevation="@dimen/qs_panel_elevation"
         android:importantForAccessibility="no"
         android:scrollbars="none"
         android:clipChildren="false"
@@ -55,7 +55,7 @@
         android:id="@+id/container_stub"
         android:inflatedId="@+id/qs_footer_actions"
         android:layout="@layout/new_footer_actions"
-        android:layout_height="@dimen/qs_footer_height"
+        android:layout_height="@dimen/new_footer_height"
         android:layout_width="match_parent"
         android:layout_gravity="bottom"
         />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 268088c..fe79f27 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -329,6 +329,9 @@
          etc. -->
     <dimen name="qs_footer_height">48dp</dimen>
 
+    <!-- 40dp (circles) + 8dp (circle padding) + 8dp (top) + 4dp (bottom) -->
+    <dimen name="new_footer_height">60dp</dimen>
+
     <!-- The size of each of the icon buttons in the QS footer -->
     <dimen name="qs_footer_action_button_size">48dp</dimen>
 
@@ -336,6 +339,9 @@
 
     <!-- (48dp - 44dp) / 2 -->
     <dimen name="qs_footer_action_inset">2dp</dimen>
+    <!-- (48dp - 40dp) / 2 -->
+    <dimen name="new_qs_footer_action_inset">4dp</dimen>
+    <dimen name="new_qs_footer_action_inset_negative">-4dp</dimen>
 
     <!-- Margins on each side of QS Footer -->
     <dimen name="qs_footer_margin">2dp</dimen>
@@ -491,7 +497,8 @@
     <dimen name="qs_tile_text_size">14sp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
     <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
-    <dimen name="qs_panel_padding_bottom">@dimen/qs_footer_height</dimen>
+    <dimen name="qs_panel_elevation">4dp</dimen>
+    <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
     <dimen name="qs_panel_padding_top">48dp</dimen>
     <dimen name="qs_detail_header_padding">0dp</dimen>
     <dimen name="qs_detail_image_width">56dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 53aebda..d39e295 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2375,6 +2375,9 @@
         <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> active app</item>
         <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> active apps</item>
     </plurals>
+    <!-- Content description for a dot indicator in the running application indicating that there
+    is new information [CHAR LIMIT=NONE] -->
+    <string name="fgs_dot_content_description">New information</string>
     <!-- Title for dialog listing applications currently running [CHAR LIMIT=NONE]-->
     <string name="fgs_manager_dialog_title">Active apps</string>
     <!-- Label of the button to stop an app from running [CHAR LIMIT=12]-->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 58ebe89..bbe0a99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -40,6 +40,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
 import com.android.systemui.R
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -50,6 +51,7 @@
 import javax.inject.Inject
 import kotlin.math.max
 
+@SysUISingleton
 class FgsManagerController @Inject constructor(
     private val context: Context,
     @Main private val mainExecutor: Executor,
@@ -65,6 +67,9 @@
         private val LOG_TAG = FgsManagerController::class.java.simpleName
     }
 
+    var changesSinceDialog = false
+        private set
+
     private var isAvailable = false
 
     private val lock = Any()
@@ -137,6 +142,7 @@
             val numPackagesAfter = getNumRunningPackagesLocked()
 
             if (numPackagesAfter != numPackagesBefore) {
+                changesSinceDialog = true
                 onNumberOfPackagesChangedListeners.forEach {
                     backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) }
                 }
@@ -210,6 +216,7 @@
                 this.dialog = dialog
 
                 dialog.setOnDismissListener {
+                    changesSinceDialog = false
                     synchronized(lock) {
                         this.dialog = null
                         updateAppItemsLocked()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 4aedbc9..77feb90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -22,6 +22,8 @@
 import android.provider.Settings
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
 import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
@@ -29,7 +31,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.flags.FeatureFlags
@@ -46,6 +47,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.DualHeightHorizontalLinearLayout
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
@@ -57,7 +59,7 @@
  * determined by [buttonsVisibleState]
  */
 @QSScope
-class FooterActionsController @Inject constructor(
+internal class FooterActionsController @Inject constructor(
     view: FooterActionsView,
     multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
     private val activityStarter: ActivityStarter,
@@ -65,6 +67,8 @@
     private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
     private val deviceProvisionedController: DeviceProvisionedController,
+    private val securityFooterController: QSSecurityFooter,
+    private val fgsManagerFooterController: QSFgsManagerFooter,
     private val falsingManager: FalsingManager,
     private val metricsLogger: MetricsLogger,
     private val tunerService: TunerService,
@@ -90,15 +94,15 @@
             updateVisibility()
         }
 
-    init {
-        view.elevation = resources.displayMetrics.density * 4f
-        view.setBackgroundColor(Utils.getColorAttrDefaultColor(context, R.attr.underSurfaceColor))
-    }
-
     private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
     private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
+    private val securityFootersContainer: ViewGroup? =
+        view.findViewById(R.id.security_footers_container)
     private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
     private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
+    private val securityFootersSeparator = View(context).apply {
+        visibility = View.GONE
+    }
 
     private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
         val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -151,6 +155,7 @@
 
     override fun onInit() {
         multiUserSwitchController.init()
+        fgsManagerFooterController.init()
     }
 
     private fun updateVisibility() {
@@ -178,9 +183,46 @@
             powerMenuLite.visibility = View.GONE
         }
         settingsButton.setOnClickListener(onClickListener)
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout
+            securityFootersContainer?.addView(securityFooter)
+            val separatorWidth = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
+            securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
+            reformatForNewFooter(securityFooter)
+            val fgsFooter = fgsManagerFooterController.view
+            securityFootersContainer?.addView(fgsFooter)
+
+            val visibilityListener =
+                VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
+                    if (visibility == View.GONE) {
+                        securityFootersSeparator.visibility = View.GONE
+                    } else if (securityFooter.visibility == View.VISIBLE &&
+                        fgsFooter.visibility == View.VISIBLE) {
+                        securityFootersSeparator.visibility = View.VISIBLE
+                    } else {
+                        securityFootersSeparator.visibility = View.GONE
+                    }
+                    fgsManagerFooterController
+                        .setCollapsed(securityFooter.visibility == View.VISIBLE)
+                }
+            securityFooterController.setOnVisibilityChangedListener(visibilityListener)
+            fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
+        }
         updateView()
     }
 
+    private fun reformatForNewFooter(view: DualHeightHorizontalLinearLayout) {
+        // This is only necessary while things are flagged as the view could be attached in two
+        // different locations.
+        (view.layoutParams as LinearLayout.LayoutParams).apply {
+            bottomMargin = 0
+            width = 0
+            weight = 1f
+            marginEnd = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
+        }
+        view.alwaysSingleLine = true
+    }
+
     private fun updateView() {
         mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
     }
@@ -201,6 +243,10 @@
         } else {
             userInfoController.removeCallback(onUserInfoChangedListener)
         }
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            fgsManagerFooterController.setListening(listening)
+            securityFooterController.setListening(listening)
+        }
     }
 
     fun disable(state2: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7800027..707313f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -211,9 +211,13 @@
                 // Some views are always full width or have dependent padding
                 continue;
             }
-            LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            lp.rightMargin = mSideMargins;
-            lp.leftMargin = mSideMargins;
+            if (!(view instanceof FooterActionsView)) {
+                // Only padding for FooterActionsView, no margin. That way, the background goes
+                // all the way to the edge.
+                LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                lp.rightMargin = mSideMargins;
+                lp.leftMargin = mSideMargins;
+            }
             if (view == mQSPanelContainer) {
                 // QS panel lays out some of its content full width
                 qsPanelController.setContentMargins(mContentPadding, mContentPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 55d4a53..0fe9095 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -20,12 +20,17 @@
 
 import android.content.Context;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.qs.dagger.QSScope;
 
 import java.util.concurrent.Executor;
 
@@ -35,9 +40,11 @@
 /**
  * Footer entry point for the foreground service manager
  */
+@QSScope
 public class QSFgsManagerFooter implements View.OnClickListener,
         FgsManagerController.OnDialogDismissedListener,
-        FgsManagerController.OnNumberOfPackagesChangedListener {
+        FgsManagerController.OnNumberOfPackagesChangedListener,
+        VisibilityChangedDispatcher {
 
     private final View mRootView;
     private final TextView mFooterText;
@@ -50,20 +57,43 @@
     private boolean mIsInitialized = false;
     private int mNumPackages;
 
+    private final View mTextContainer;
+    private final View mNumberContainer;
+    private final TextView mNumberView;
+    private final ImageView mDotView;
+
+    @Nullable
+    private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
+
     @Inject
     QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
             @Main Executor mainExecutor, @Background Executor executor,
             FgsManagerController fgsManagerController) {
         mRootView = rootView;
         mFooterText = mRootView.findViewById(R.id.footer_text);
-        ImageView icon = mRootView.findViewById(R.id.primary_footer_icon);
-        icon.setImageResource(R.drawable.ic_info_outline);
+        mTextContainer = mRootView.findViewById(R.id.fgs_text_container);
+        mNumberContainer = mRootView.findViewById(R.id.fgs_number_container);
+        mNumberView = mRootView.findViewById(R.id.fgs_number);
+        mDotView = mRootView.findViewById(R.id.fgs_new);
         mContext = rootView.getContext();
         mMainExecutor = mainExecutor;
         mExecutor = executor;
         mFgsManagerController = fgsManagerController;
     }
 
+    /**
+     * Whether to show the footer in collapsed mode (just a number) or not (text).
+     * @param collapsed
+     */
+    public void setCollapsed(boolean collapsed) {
+        mTextContainer.setVisibility(collapsed ? View.GONE : View.VISIBLE);
+        mNumberContainer.setVisibility(collapsed ? View.VISIBLE : View.GONE);
+        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRootView.getLayoutParams();
+        lp.width = collapsed ? ViewGroup.LayoutParams.WRAP_CONTENT : 0;
+        lp.weight = collapsed ? 0f : 1f;
+        mRootView.setLayoutParams(lp);
+    }
+
     public void init() {
         if (mIsInitialized) {
             return;
@@ -89,6 +119,12 @@
     }
 
     @Override
+    public void setOnVisibilityChangedListener(
+            @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
+        mVisibilityChangedListener = onVisibilityChangedListener;
+    }
+
+    @Override
     public void onClick(View view) {
         mFgsManagerController.showDialog(mRootView);
     }
@@ -103,11 +139,19 @@
 
     public void handleRefreshState() {
         mMainExecutor.execute(() -> {
-            mFooterText.setText(mContext.getResources().getQuantityString(
-                    R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages));
+            CharSequence text = mContext.getResources().getQuantityString(
+                    R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages);
+            mFooterText.setText(text);
+            mNumberView.setText(Integer.toString(mNumPackages));
+            mNumberView.setContentDescription(text);
             if (mFgsManagerController.shouldUpdateFooterVisibility()) {
                 mRootView.setVisibility(mNumPackages > 0
                         && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE);
+                mDotView.setVisibility(
+                        mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE);
+                if (mVisibilityChangedListener != null) {
+                    mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
+                }
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 6b515c8..7c04cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -110,6 +110,7 @@
     private float mSquishinessFraction = 1f;
     private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
     private final Rect mClippingRect = new Rect();
+    private boolean mUseNewFooter = false;
 
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -151,6 +152,10 @@
         }
     }
 
+    void setUseNewFooter(boolean useNewFooter) {
+        mUseNewFooter = useNewFooter;
+    }
+
     protected void setHorizontalContentContainerClipping() {
         mHorizontalContentContainer.setClipChildren(true);
         mHorizontalContentContainer.setClipToPadding(false);
@@ -368,11 +373,12 @@
 
     protected void updatePadding() {
         final Resources res = mContext.getResources();
-        int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+        int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+        // Bottom padding only when there's a new footer with its height.
         setPaddingRelative(getPaddingStart(),
-                padding,
+                paddingTop,
                 getPaddingEnd(),
-                res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
+                mUseNewFooter ? res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom) : 0);
     }
 
     void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 8f268b5..418c4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -31,6 +31,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
@@ -67,6 +69,7 @@
     private final BrightnessController mBrightnessController;
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
+    private final FeatureFlags mFeatureFlags;
 
     private boolean mGridContentVisible = true;
 
@@ -104,7 +107,7 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
-            FalsingManager falsingManager, CommandQueue commandQueue) {
+            FalsingManager falsingManager, CommandQueue commandQueue, FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQSFgsManagerFooter = qsFgsManagerFooter;
@@ -114,13 +117,14 @@
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
         mFalsingManager = falsingManager;
         mCommandQueue = commandQueue;
-        mQsSecurityFooter.setHostEnvironment(qstileHost);
 
         mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
         mView.setBrightnessView(mBrightnessSliderController.getRootView());
 
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
+        mFeatureFlags = featureFlags;
+        view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER));
     }
 
     @Override
@@ -150,8 +154,10 @@
             refreshAllTiles();
         }
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
-        mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
-        mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
+        if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
+            mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
+        }
         switchTileLayout(true);
         mBrightnessMirrorHandler.onQsPanelAttached();
 
@@ -192,8 +198,10 @@
             refreshAllTiles();
         }
 
-        mQSFgsManagerFooter.setListening(listening);
-        mQsSecurityFooter.setListening(listening);
+        if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            mQSFgsManagerFooter.setListening(listening);
+            mQsSecurityFooter.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.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 9e17c12..fb55cd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -80,6 +80,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -88,11 +89,14 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.SecurityController;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
 @QSScope
-class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener {
+class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener,
+        VisibilityChangedDispatcher {
     protected static final String TAG = "QSSecurityFooter";
     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean DEBUG_FORCE_VISIBLE = false;
@@ -107,11 +111,16 @@
     private final ActivityStarter mActivityStarter;
     private final Handler mMainHandler;
     private final UserTracker mUserTracker;
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+    private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false);
 
     private AlertDialog mDialog;
-    private QSTileHost mHost;
     protected H mHandler;
 
+    // Does it move between footer and header? Remove this once all the flagging is removed
+    private boolean mIsMovable = true;
+
     private boolean mIsVisible;
     @Nullable
     private CharSequence mFooterTextContent = null;
@@ -119,10 +128,14 @@
     @Nullable
     private Drawable mPrimaryFooterIconDrawable;
 
+    @Nullable
+    private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
+
     @Inject
     QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView,
             UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
-            SecurityController securityController, @Background Looper bgLooper) {
+            SecurityController securityController, DialogLaunchAnimator dialogLaunchAnimator,
+            @Background Looper bgLooper) {
         mRootView = rootView;
         mRootView.setOnClickListener(this);
         mFooterText = mRootView.findViewById(R.id.footer_text);
@@ -135,10 +148,7 @@
         mSecurityController = securityController;
         mHandler = new H(bgLooper);
         mUserTracker = userTracker;
-    }
-
-    public void setHostEnvironment(QSTileHost host) {
-        mHost = host;
+        mDialogLaunchAnimator = dialogLaunchAnimator;
     }
 
     public void setListening(boolean listening) {
@@ -150,23 +160,31 @@
         }
     }
 
+    @Override
+    public void setOnVisibilityChangedListener(
+            @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
+        mVisibilityChangedListener = onVisibilityChangedListener;
+    }
+
     public void onConfigurationChanged() {
         FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size);
 
-        Resources r = mContext.getResources();
+        if (mIsMovable) {
+            Resources r = mContext.getResources();
 
-        mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines));
-        int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
-        mRootView.setPaddingRelative(padding, padding, padding, padding);
+            mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines));
+            int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
+            mRootView.setPaddingRelative(padding, padding, padding, padding);
 
-        int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
-        ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams();
-        lp.bottomMargin = bottomMargin;
-        lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT
-                ? MATCH_PARENT : WRAP_CONTENT;
-        mRootView.setLayoutParams(lp);
+            int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
+            ViewGroup.MarginLayoutParams lp =
+                    (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams();
+            lp.bottomMargin = bottomMargin;
+            lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT
+                    ? MATCH_PARENT : WRAP_CONTENT;
+            mRootView.setLayoutParams(lp);
 
+        }
         mRootView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background));
     }
 
@@ -455,23 +473,27 @@
     public void onClick(DialogInterface dialog, int which) {
         if (which == DialogInterface.BUTTON_NEGATIVE) {
             final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS);
-            mDialog.dismiss();
+            dialog.dismiss();
             // This dismisses the shade on opening the activity
             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
         }
     }
 
     private void createDialog() {
-        mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme
-        mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
-        mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
-        mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
+        mShouldUseSettingsButton.set(false);
+        final View view = createDialogView();
+        mMainHandler.post(() -> {
+            mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme
+            mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+            mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
+            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+                    mShouldUseSettingsButton.get() ? getSettingsButton() : getNegativeButton(),
+                    this);
 
-        mDialog.setView(createDialogView());
+            mDialog.setView(view);
 
-        mDialog.show();
-        mDialog.getWindow().setLayout(MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
+            mDialogLaunchAnimator.showFromView(mDialog, mRootView);
+        });
     }
 
     @VisibleForTesting
@@ -510,7 +532,7 @@
             TextView deviceManagementWarning =
                     (TextView) dialogView.findViewById(R.id.device_management_warning);
             deviceManagementWarning.setText(managementMessage);
-            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
+            mShouldUseSettingsButton.set(true);
         }
 
         // ca certificate section
@@ -782,6 +804,9 @@
                 mFooterText.setText(mFooterTextContent);
             }
             mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE);
+            if (mVisibilityChangedListener != null) {
+                mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
+            }
         }
     };
 
@@ -814,7 +839,6 @@
             } catch (Throwable t) {
                 final String error = "Error in " + name;
                 Log.w(TAG, error, t);
-                mHost.warn(error, t);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt b/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt
new file mode 100644
index 0000000..73362ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.qs
+
+/**
+ * Dispatches events that set the visibility from the controller.
+ */
+interface VisibilityChangedDispatcher {
+
+    fun setOnVisibilityChangedListener(onVisibilityChangedListener: OnVisibilityChangedListener?)
+
+    fun interface OnVisibilityChangedListener {
+        fun onVisibilityChanged(visibility: Int)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index fdf9ae0..2780b16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -202,6 +202,6 @@
             @QSThemedContext LayoutInflater layoutInflater,
             QSPanel qsPanel
     ) {
-        return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+        return layoutInflater.inflate(R.layout.fgs_footer, qsPanel, false);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index b457ebf..7c9e597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -102,17 +102,21 @@
     private fun updateBottomSpacing() {
         val (containerPadding, notificationsMargin) = calculateBottomSpacing()
         var qsScrollPaddingBottom = 0
-        if (!(splitShadeEnabled || isQSCustomizing || isQSDetailShowing || isGestureNavigation ||
-                        taskbarVisible)) {
+        val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
+        if (!newFooter && !(splitShadeEnabled || isQSCustomizing || isQSDetailShowing ||
+                        isGestureNavigation || taskbarVisible)) {
             // no taskbar, portrait, navigation buttons enabled:
             // padding is needed so QS can scroll up over bottom insets - to reach the point when
             // the whole QS is above bottom insets
             qsScrollPaddingBottom = bottomStableInsets
+        } else if (newFooter && !(isQSCustomizing || isQSDetailShowing)) {
+            // With the new footer, we also want this padding in the bottom in these cases
+            qsScrollPaddingBottom = bottomStableInsets
         }
         mView.setPadding(0, 0, 0, containerPadding)
         mView.setNotificationsMarginBottom(notificationsMargin)
-        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            mView.setQSContainerPaddingBottom(notificationsMargin)
+        if (newFooter) {
+            mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
         } else {
             mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
index 0e04871..cfceefa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
@@ -65,6 +65,17 @@
 
     private var initialPadding = mPaddingTop // All vertical padding is the same
 
+    private var originalMaxLines = 1
+    var alwaysSingleLine: Boolean = false
+        set(value) {
+            field = value
+            if (field) {
+                textView?.setSingleLine()
+            } else {
+                textView?.maxLines = originalMaxLines
+            }
+        }
+
     init {
         if (orientation != HORIZONTAL) {
             throw IllegalStateException("This view should always have horizontal orientation")
@@ -120,7 +131,7 @@
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         textView?.let { tv ->
-            if (tv.lineCount < 2) {
+            if (tv.lineCount < 2 || alwaysSingleLine) {
                 setMeasuredDimension(measuredWidth, singleLineHeightPx)
                 mPaddingBottom = 0
                 mPaddingTop = 0
@@ -133,7 +144,9 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        textView = findViewById(textViewId)
+        textView = findViewById<TextView>(textViewId)?.also {
+            originalMaxLines = it.maxLines
+        }
     }
 
     override fun onConfigurationChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index f5fa0d0..91a9f9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -64,6 +64,10 @@
     private lateinit var uiEventLogger: UiEventLogger
     @Mock
     private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var securityFooterController: QSSecurityFooter
+    @Mock
+    private lateinit var fgsManagerController: QSFgsManagerFooter
 
     private lateinit var controller: FooterActionsController
 
@@ -90,7 +94,8 @@
 
         controller = FooterActionsController(view, multiUserSwitchControllerFactory,
                 activityStarter, userManager, userTracker, userInfoController,
-                deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
+                deviceProvisionedController, securityFooterController, fgsManagerController,
+                falsingManager, metricsLogger, fakeTunerService,
                 globalActionsDialog, uiEventLogger, showPMLiteButton = true, fakeSettings,
                 Handler(testableLooper.looper), featureFlags)
         controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index b5ce706..b2ca62f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizerController;
@@ -106,6 +107,8 @@
     Resources mResources;
     @Mock
     Configuration mConfiguration;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     private QSPanelController mController;
 
@@ -133,7 +136,7 @@
                 mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                mFalsingManager, mCommandQueue
+                mFalsingManager, mCommandQueue, mFeatureFlags
         );
 
         mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 3500c18..4ae19332 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -162,6 +162,20 @@
         assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
     }
 
+    @Test
+    fun testBottomPadding() {
+        mQsPanel.setUseNewFooter(false)
+
+        mQsPanel.updatePadding()
+        assertThat(mQsPanel.paddingBottom).isEqualTo(0)
+
+        mQsPanel.setUseNewFooter(true)
+
+        mQsPanel.updatePadding()
+        assertThat(mQsPanel.paddingBottom)
+                .isEqualTo(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
+    }
+
     private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
             context.resources.configuration.apply { orientation = newOrientation }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 770cf2c..2b7fa42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -22,12 +22,14 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AlertDialog;
 import android.content.ComponentName;
 import android.content.DialogInterface;
 import android.content.pm.UserInfo;
@@ -50,6 +52,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.SecurityController;
@@ -57,10 +60,13 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /*
  * Compile and run the whole SystemUI test suite:
    runtest --path frameworks/base/packages/SystemUI/tests
@@ -94,20 +100,24 @@
     private UserTracker mUserTracker;
     @Mock
     private ActivityStarter mActivityStarter;
+    @Mock
+    private DialogLaunchAnimator mDialogLaunchAnimator;
+
+    private TestableLooper mTestableLooper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Looper looper = TestableLooper.get(this).getLooper();
+        mTestableLooper = TestableLooper.get(this);
+        Looper looper = mTestableLooper.getLooper();
         when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
         mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
                 .replace("ImageView", TestableImageView.class)
                 .build().inflate(R.layout.quick_settings_security_footer, null, false);
         mFooter = new QSSecurityFooter(mRootView, mUserTracker, new Handler(looper),
-                mActivityStarter, mSecurityController, looper);
+                mActivityStarter, mSecurityController, mDialogLaunchAnimator, looper);
         mFooterText = mRootView.findViewById(R.id.footer_text);
         mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
-        mFooter.setHostEnvironment(null);
 
         when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
                 .thenReturn(DEVICE_OWNER_COMPONENT);
@@ -650,8 +660,6 @@
 
     @Test
     public void testNoClickWhenGone() {
-        QSTileHost mockHost = mock(QSTileHost.class);
-        mFooter.setHostEnvironment(mockHost);
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
@@ -660,7 +668,7 @@
         mFooter.onClick(mFooter.getView());
 
         // Proxy for dialog being created
-        verify(mockHost, never()).collapsePanels();
+        verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
     }
 
     @Test
@@ -700,6 +708,16 @@
     }
 
     @Test
+    public void testDialogUsesDialogLauncher() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        mFooter.onClick(mRootView);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView));
+    }
+
+    @Test
     public void testCreateDialogViewForFinancedDevice() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
@@ -707,12 +725,6 @@
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
-        // Initialize AlertDialog which sets the text for the negative button, which is used when
-        // creating the dialog for a financed device.
-        mFooter.showDeviceMonitoringDialog();
-        // The above statement would display the Quick Settings dialog which requires user input,
-        // so simulate the press to continue with the unit test (otherwise, it is stuck).
-        mFooter.onClick(null, DialogInterface.BUTTON_NEGATIVE);
         View view = mFooter.createDialogView();
 
         TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle);
@@ -727,6 +739,49 @@
                 mFooter.getSettingsButton());
     }
 
+    @Test
+    public void testFinancedDeviceUsesSettingsButtonText() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.getDeviceOwnerOrganizationName())
+                .thenReturn(MANAGING_ORGANIZATION);
+        when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+                .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+        mFooter.showDeviceMonitoringDialog();
+        ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
+
+        mTestableLooper.processAllMessages();
+        verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any());
+
+        AlertDialog dialog = dialogCaptor.getValue();
+        dialog.create();
+
+        assertEquals(mFooter.getSettingsButton(),
+                dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText());
+
+        dialog.dismiss();
+    }
+
+    @Test
+    public void testVisibilityListener() {
+        final AtomicInteger lastVisibility = new AtomicInteger(-1);
+        VisibilityChangedDispatcher.OnVisibilityChangedListener listener =
+                (VisibilityChangedDispatcher.OnVisibilityChangedListener) lastVisibility::set;
+
+        mFooter.setOnVisibilityChangedListener(listener);
+
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        mFooter.refreshState();
+        mTestableLooper.processAllMessages();
+        assertEquals(View.VISIBLE, lastVisibility.get());
+
+        when(mSecurityController.isDeviceManaged()).thenReturn(false);
+        mFooter.refreshState();
+        mTestableLooper.processAllMessages();
+        assertEquals(View.GONE, lastVisibility.get());
+    }
+
+
     private CharSequence addLink(CharSequence description) {
         final SpannableStringBuilder message = new SpannableStringBuilder();
         message.append(description);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index bbb2346..00af446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -114,13 +114,15 @@
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = true,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -150,13 +152,15 @@
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
+        then(expectedContainerPadding = 0,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -190,7 +194,8 @@
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withCutout().withStableBottom())
         then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -217,12 +222,14 @@
         given(taskbarVisible = true,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
+        then(expectedContainerPadding = 0,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = true,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM)
+        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -259,7 +266,7 @@
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = CUTOUT_HEIGHT)
+        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
@@ -350,6 +357,7 @@
     @Test
     fun testDetailShowingInSplitShade() {
         notificationsQSContainerController.splitShadeEnabled = true
+        notificationsQSContainerController.setDetailShowing(true)
         useNewFooter(false)
 
         given(taskbarVisible = false,
@@ -357,7 +365,6 @@
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0)
 
-        notificationsQSContainerController.setDetailShowing(true)
         // should not influence spacing
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
@@ -368,6 +375,7 @@
     @Test
     fun testDetailShowingInSplitShade_newFooter() {
         notificationsQSContainerController.splitShadeEnabled = true
+        notificationsQSContainerController.setDetailShowing(true)
         useNewFooter(true)
 
         given(taskbarVisible = false,
@@ -375,7 +383,6 @@
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0)
 
-        notificationsQSContainerController.setDetailShowing(true)
         // should not influence spacing
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
@@ -417,7 +424,7 @@
         val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
         if (newFooter) {
             verify(notificationsQSContainer)
-                    .setQSContainerPaddingBottom(expectedNotificationsMargin)
+                    .setQSContainerPaddingBottom(expectedQsPadding)
         } else {
             verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
         }