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 0000000000000..5343411e4c958 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + \ 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 95bdd8948c7cb..4884df7f838e7 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" > - + /> + @@ -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"> 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 0000000000000..759ddaf1e73f7 --- /dev/null +++ b/packages/SystemUI/res/drawable/new_fgs_dot.xml @@ -0,0 +1,22 @@ + + + + + \ 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 f54c30f2e7949..d057f5f32dc4a 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. --> + android:inset="@dimen/new_qs_footer_action_inset"> 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 1a323bccfa654..944061cc8e701 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. --> + android:inset="@dimen/new_qs_footer_action_inset"> 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 0000000000000..c9517cd905dc9 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml index 860d23b114391..381af503d47c0 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. --> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 22abd0c2f0346..85b33cc4cc257 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 268088cb2607f..fe79f271d0b64 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -329,6 +329,9 @@ etc. --> 48dp + + 60dp + 48dp @@ -336,6 +339,9 @@ 2dp + + 4dp + -4dp 2dp @@ -491,7 +497,8 @@ 14sp 16dp 6dp - @dimen/qs_footer_height + 4dp + @dimen/new_footer_height 48dp 0dp 56dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 53aebda8a4b4e..d39e295689864 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2375,6 +2375,9 @@ %s active app %s active apps + + New information Active apps diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 58ebe89f199a1..bbe0a99309c63 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 androidx.recyclerview.widget.RecyclerView 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 java.util.concurrent.Executor 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 @@ class FgsManagerController @Inject constructor( 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 @@ class FgsManagerController @Inject constructor( val numPackagesAfter = getNumRunningPackagesLocked() if (numPackagesAfter != numPackagesBefore) { + changesSinceDialog = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) } } @@ -210,6 +216,7 @@ class FgsManagerController @Inject constructor( 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 4aedbc983d9c2..77feb90f575a8 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.os.UserManager 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.MetricsLogger 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.DeviceProvisionedController 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 @@ import javax.inject.Named * 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 @@ class FooterActionsController @Inject constructor( 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 @@ class FooterActionsController @Inject constructor( 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 @@ class FooterActionsController @Inject constructor( override fun onInit() { multiUserSwitchController.init() + fgsManagerFooterController.init() } private fun updateVisibility() { @@ -178,9 +183,46 @@ class FooterActionsController @Inject constructor( 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 @@ class FooterActionsController @Inject constructor( } 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 7800027ea967f..707313f08af60 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 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { // 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 55d4a53ced7b2..0fe909552cb15 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 static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOO 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 @@ import javax.inject.Named; /** * 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 @@ public class QSFgsManagerFooter implements View.OnClickListener, 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; @@ -88,6 +118,12 @@ public class QSFgsManagerFooter implements View.OnClickListener, } } + @Override + public void setOnVisibilityChangedListener( + @Nullable OnVisibilityChangedListener onVisibilityChangedListener) { + mVisibilityChangedListener = onVisibilityChangedListener; + } + @Override public void onClick(View view) { mFgsManagerController.showDialog(mRootView); @@ -103,11 +139,19 @@ public class QSFgsManagerFooter implements View.OnClickListener, 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 6b515c85dc9f2..7c04cd4dcb059 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 @@ public class QSPanel extends LinearLayout implements Tunable { private float mSquishinessFraction = 1f; private final ArrayMap 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 @@ public class QSPanel extends LinearLayout implements Tunable { } } + void setUseNewFooter(boolean useNewFooter) { + mUseNewFooter = useNewFooter; + } + protected void setHorizontalContentContainerClipping() { mHorizontalContentContainer.setClipChildren(true); mHorizontalContentContainer.setClipToPadding(false); @@ -368,11 +373,12 @@ public class QSPanel extends LinearLayout implements Tunable { 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 8f268b5cffe4c..418c4ae023ca0 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.MetricsLogger; 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 @@ public class QSPanelController extends QSPanelControllerBase { private final BrightnessController mBrightnessController; private final BrightnessSliderController mBrightnessSliderController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; + private final FeatureFlags mFeatureFlags; private boolean mGridContentVisible = true; @@ -104,7 +107,7 @@ public class QSPanelController extends QSPanelControllerBase { 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 @@ public class QSPanelController extends QSPanelControllerBase { 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 @@ public class QSPanelController extends QSPanelControllerBase { 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 @@ public class QSPanelController extends QSPanelControllerBase { 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 9e17c121a79f2..fb55cd2d22453 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 androidx.annotation.VisibleForTesting; 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.settings.UserTracker; 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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen 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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen @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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen 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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen } } + @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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen 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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen 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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen mFooterText.setText(mFooterTextContent); } mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE); + if (mVisibilityChangedListener != null) { + mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility()); + } } }; @@ -814,7 +839,6 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen } 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 0000000000000..73362cec701e9 --- /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 fdf9ae0d4b635..2780b163e5ca0 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 @@ public interface QSFragmentModule { @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 b457ebf60c716..7c9e597e74a13 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 @@ class NotificationsQSContainerController @Inject constructor( 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 0e04871106ef8..cfceefa2006c4 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 @@ class DualHeightHorizontalLinearLayout @JvmOverloads constructor( 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 @@ class DualHeightHorizontalLinearLayout @JvmOverloads constructor( 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 @@ class DualHeightHorizontalLinearLayout @JvmOverloads constructor( override fun onFinishInflate() { super.onFinishInflate() - textView = findViewById(textViewId) + textView = findViewById(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 f5fa0d030901e..91a9f9e5ef36d 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 @@ class FooterActionsControllerTest : LeakCheckedTest() { 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 @@ class FooterActionsControllerTest : LeakCheckedTest() { 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 b5ce7066f5529..b2ca62f18f27c 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.internal.logging.testing.UiEventLoggerFake; 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 @@ public class QSPanelControllerTest extends SysuiTestCase { Resources mResources; @Mock Configuration mConfiguration; + @Mock + FeatureFlags mFeatureFlags; private QSPanelController mController; @@ -133,7 +136,7 @@ public class QSPanelControllerTest extends SysuiTestCase { 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 3500c183de394..4ae193327b7bc 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 @@ class QSPanelTest : SysuiTestCase() { 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 770cf2ca03313..2b7fa42903c4b 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 junit.framework.Assert.assertNotNull; 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 android.widget.TextView; 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 com.android.systemui.statusbar.policy.SecurityController; 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 @@ public class QSSecurityFooterTest extends SysuiTestCase { 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 @@ public class QSSecurityFooterTest extends SysuiTestCase { @Test public void testNoClickWhenGone() { - QSTileHost mockHost = mock(QSTileHost.class); - mFooter.setHostEnvironment(mockHost); mFooter.refreshState(); TestableLooper.get(this).processAllMessages(); @@ -660,7 +668,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { mFooter.onClick(mFooter.getView()); // Proxy for dialog being created - verify(mockHost, never()).collapsePanels(); + verify(mDialogLaunchAnimator, never()).showFromView(any(), any()); } @Test @@ -699,6 +707,16 @@ public class QSSecurityFooterTest extends SysuiTestCase { assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText()); } + @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); @@ -707,12 +725,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { 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 @@ public class QSSecurityFooterTest extends SysuiTestCase { 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 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 bbb2346797b98..00af446ded3d1 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 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { 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 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { 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 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { 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 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { 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 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { 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 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSplitShade() { notificationsQSContainerController.splitShadeEnabled = true + notificationsQSContainerController.setDetailShowing(true) useNewFooter(false) given(taskbarVisible = false, @@ -357,7 +365,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { insets = windowInsets().withStableBottom()) then(expectedContainerPadding = 0) - notificationsQSContainerController.setDetailShowing(true) // should not influence spacing given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, @@ -368,6 +375,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSplitShade_newFooter() { notificationsQSContainerController.splitShadeEnabled = true + notificationsQSContainerController.setDetailShowing(true) useNewFooter(true) given(taskbarVisible = false, @@ -375,7 +383,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { insets = windowInsets().withStableBottom()) then(expectedContainerPadding = 0) - notificationsQSContainerController.setDetailShowing(true) // should not influence spacing given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, @@ -417,7 +424,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER) if (newFooter) { verify(notificationsQSContainer) - .setQSContainerPaddingBottom(expectedNotificationsMargin) + .setQSContainerPaddingBottom(expectedQsPadding) } else { verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding) }