Create QS footer entry point for fgs manager

Bug: 201579707
Test: `atest SystemUITests`

Change-Id: If393e54f5b5ed24123365836f5c0554899730a31
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 08fb2c6..a01625c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2345,6 +2345,11 @@
     <!-- Title for User Switch dialog. [CHAR LIMIT=20] -->
     <string name="qs_user_switch_dialog_title">Select user</string>
 
+    <!-- Label for the entry point to open the dialog which shows currently running applications [CHAR LIMIT=NONE]-->
+    <plurals name="fgs_manager_footer_label">
+        <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> app running in the background</item>
+        <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> apps running in the background</item>
+    </plurals>
     <!-- Title for dialog listing applications currently running in the backing [CHAR LIMIT=NONE]-->
     <string name="fgs_manager_dialog_title">Apps running in the background</string>
     <!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]-->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 1dba536..ded6ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -85,6 +85,7 @@
     private final QSPanelController mQsPanelController;
     private final QuickQSPanelController mQuickQSPanelController;
     private final QuickStatusBarHeader mQuickStatusBarHeader;
+    private final QSFgsManagerFooter mFgsManagerFooter;
     private final QSSecurityFooter mSecurityFooter;
     private final QS mQs;
     private final View mQSFooterActions;
@@ -151,7 +152,8 @@
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
             QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
-            QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
+            QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
+            @Main Executor executor, TunerService tunerService,
             QSExpansionPathInterpolator qsExpansionPathInterpolator,
             @Named(QS_FOOTER) FooterActionsView qsFooterActionsView,
             @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
@@ -162,6 +164,7 @@
         mQuickStatusBarHeader = quickStatusBarHeader;
         mQQSFooterActions = qqsFooterActionsView;
         mQSFooterActions = qsFooterActionsView;
+        mFgsManagerFooter = fgsManagerFooter;
         mSecurityFooter = securityFooter;
         mHost = qsTileHost;
         mExecutor = executor;
@@ -481,6 +484,7 @@
 
             // Fade in the security footer and the divider as we reach the final position
             Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
+            builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
             builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
             if (mQsPanelController.shouldUseHorizontalLayout()
                     && mQsPanelController.mMediaHost.hostView != null) {
@@ -490,6 +494,7 @@
                 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
             }
             mAllPagesDelayedAnimator = builder.build();
+            mAllViews.add(mFgsManagerFooter.getView());
             mAllViews.add(mSecurityFooter.getView());
             translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
             qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
new file mode 100644
index 0000000..082de16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.fgsmanager.FgsManagerDialogFactory;
+import com.android.systemui.statusbar.policy.RunningFgsController;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Footer entry point for the foreground service manager
+ */
+public class QSFgsManagerFooter implements View.OnClickListener {
+
+    private final View mRootView;
+    private final TextView mFooterText;
+    private final Context mContext;
+    private final Executor mMainExecutor;
+    private final Executor mExecutor;
+    private final RunningFgsController mRunningFgsController;
+    private final FgsManagerDialogFactory mFgsManagerDialogFactory;
+
+    private boolean mIsInitialized = false;
+    private boolean mIsAvailable = false;
+
+    @Inject
+    QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
+            @Main Executor mainExecutor, RunningFgsController runningFgsController,
+            @Background Executor executor,
+            FgsManagerDialogFactory fgsManagerDialogFactory) {
+        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);
+        mContext = rootView.getContext();
+        mMainExecutor = mainExecutor;
+        mExecutor = executor;
+        mRunningFgsController = runningFgsController;
+        mFgsManagerDialogFactory = fgsManagerDialogFactory;
+    }
+
+    public void init() {
+        if (mIsInitialized) {
+            return;
+        }
+
+        mRootView.setOnClickListener(this);
+
+        mRunningFgsController.addCallback(packages -> refreshState());
+
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, mExecutor,
+                (DeviceConfig.OnPropertiesChangedListener) properties -> {
+                    mIsAvailable = properties.getBoolean(TASK_MANAGER_ENABLED, mIsAvailable);
+                });
+        mIsAvailable = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false);
+
+        mIsInitialized = true;
+    }
+
+    @Override
+    public void onClick(View view) {
+        mFgsManagerDialogFactory.create(mRootView);
+    }
+
+    public void refreshState() {
+        mExecutor.execute(this::handleRefreshState);
+    }
+
+    public View getView() {
+        return mRootView;
+    }
+
+    private boolean isAvailable() {
+        return mIsAvailable;
+    }
+
+    public void handleRefreshState() {
+        int numPackages = mRunningFgsController.getPackagesWithFgs().size();
+        mMainExecutor.execute(() -> {
+            mFooterText.setText(mContext.getResources().getQuantityString(
+                    R.plurals.fgs_manager_footer_label, numPackages, numPackages));
+            mRootView.setVisibility(numPackages > 0 && isAvailable() ? View.VISIBLE : View.GONE);
+        });
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 38061a8..6b515c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -86,6 +86,8 @@
             new ArrayList<>();
 
     @Nullable
+    protected View mFgsManagerFooter;
+    @Nullable
     protected View mSecurityFooter;
 
     @Nullable
@@ -448,7 +450,12 @@
             switchToParent(mSecurityFooter, mHeaderContainer, 0);
         } else {
             // Add after the footer
-            int index = indexOfChild(mFooter);
+            int index;
+            if (mFgsManagerFooter != null) {
+                index = indexOfChild(mFgsManagerFooter);
+            } else {
+                index = indexOfChild(mFooter);
+            }
             switchToParent(mSecurityFooter, this, index + 1);
         }
     }
@@ -722,6 +729,17 @@
         switchSecurityFooter(shouldUseSplitNotificationShade);
     }
 
+    /**
+     * Set the fgs manager footer view and switch it into the right place
+     * @param view the view in question
+     */
+    public void setFgsManagerFooter(View view) {
+        mFgsManagerFooter = view;
+        // Add after the footer
+        int index = indexOfChild(mFooter);
+        switchToParent(mFgsManagerFooter, this, index + 1);
+    }
+
     protected void setPageMargin(int pageMargin) {
         if (mTileLayout instanceof PagedTileLayout) {
             ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 001c740e..cbfe944 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -57,6 +57,7 @@
 public class QSPanelController extends QSPanelControllerBase<QSPanel> {
     public static final String QS_REMOVE_LABELS = "sysui_remove_labels";
 
+    private final QSFgsManagerFooter mQSFgsManagerFooter;
     private final QSSecurityFooter mQsSecurityFooter;
     private final TunerService mTunerService;
     private final QSCustomizerController mQsCustomizerController;
@@ -94,7 +95,8 @@
     };
 
     @Inject
-    QSPanelController(QSPanel view, QSSecurityFooter qsSecurityFooter, TunerService tunerService,
+    QSPanelController(QSPanel view, QSFgsManagerFooter qsFgsManagerFooter,
+            QSSecurityFooter qsSecurityFooter, TunerService tunerService,
             QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QS_PANEL) MediaHost mediaHost,
@@ -105,6 +107,7 @@
             FalsingManager falsingManager, CommandQueue commandQueue) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
+        mQSFgsManagerFooter = qsFgsManagerFooter;
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
@@ -128,6 +131,7 @@
         mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
         mQsCustomizerController.init();
         mBrightnessSliderController.init();
+        mQSFgsManagerFooter.init();
     }
 
     private void updateMediaExpansion() {
@@ -146,6 +150,7 @@
             refreshAllTiles();
         }
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+        mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
         mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
         switchTileLayout(true);
         mBrightnessMirrorHandler.onQsPanelAttached();
@@ -230,6 +235,7 @@
     public void refreshAllTiles() {
         mBrightnessController.checkRestrictionAndSetEnabled();
         super.refreshAllTiles();
+        mQSFgsManagerFooter.refreshState();
         mQsSecurityFooter.refreshState();
     }
 
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 11e5b6e..1958caf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -53,6 +53,7 @@
  */
 @Module
 public interface QSFragmentModule {
+    String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
     String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
     String QQS_FOOTER = "qqs_footer";
     String QS_FOOTER = "qs_footer";
@@ -205,4 +206,15 @@
     static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
         return qsHeader.findViewById(R.id.statusIcons);
     }
+
+    /** */
+    @Provides
+    @QSScope
+    @Named(QS_FGS_MANAGER_FOOTER_VIEW)
+    static View providesQSFgsManagerFooterView(
+            @QSThemedContext LayoutInflater layoutInflater,
+            QSPanel qsPanel
+    ) {
+        return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+    }
 }
\ No newline at end of file
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 3242adb..b5ce706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -80,6 +80,8 @@
     @Mock
     private TunerService mTunerService;
     @Mock
+    private QSFgsManagerFooter mQSFgsManagerFooter;
+    @Mock
     private QSSecurityFooter mQSSecurityFooter;
     @Mock
     private QSLogger mQSLogger;
@@ -127,8 +129,8 @@
                 .thenReturn(mQSTileRevealController);
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
 
-        mController = new QSPanelController(mQSPanel, mQSSecurityFooter, mTunerService,
-                mQSTileHost, mQSCustomizerController, true, mMediaHost,
+        mController = new QSPanelController(mQSPanel, mQSFgsManagerFooter, mQSSecurityFooter,
+                mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
                 mFalsingManager, mCommandQueue