Put security footers in new footer

This CL puts the security footer and fgs disclaimer in the new footer
(based on flag) instead of QSPanel.

The FGS disclaimer has two modes. A text mode (when security footer is
not visible) or just a number with maybe an update dot (when security
footer is visible).

Also, use DialogLaunchAnimator for QSSecurityFooter

Test: manual
Test: atest SystemUITests
Fixes: 217908230
Change-Id: Id9289826de3256ea6fdfea3ca6f8bf1976364e61
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..5343411e
--- /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 175e45c..4884df7 100644
--- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
@@ -29,11 +29,13 @@
     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
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_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/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e5cabb0..bdc9dbd 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 eb341563..d26c1c5 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 92e3c3c..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
@@ -45,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
@@ -56,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,
@@ -64,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,
@@ -91,8 +96,13 @@
 
     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())
@@ -145,6 +155,7 @@
 
     override fun onInit() {
         multiUserSwitchController.init()
+        fgsManagerFooterController.init()
     }
 
     private fun updateVisibility() {
@@ -172,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)
     }
@@ -195,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/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/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 03149e3..418c4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -69,6 +69,7 @@
     private final BrightnessController mBrightnessController;
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
+    private final FeatureFlags mFeatureFlags;
 
     private boolean mGridContentVisible = true;
 
@@ -116,13 +117,13 @@
         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));
     }
 
@@ -153,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();
 
@@ -195,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 776ee10..816a387 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -193,6 +193,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/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/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);