From 7394063883e7abce53f1302d76693c32902e45c0 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 3 Feb 2022 11:10:45 -0500 Subject: [PATCH 1/2] Implement correct dimens for new footer Bug: 215686414 Test: manual in different devices and configs Test: atest SystemUITests Change-Id: If9cc89dff3225e57d92a930aab0cad0f3985c962 --- .../layout/new_footer_actions.xml | 19 +++++++----- .../res/drawable/qs_footer_action_circle.xml | 5 +--- .../qs_footer_action_circle_color.xml | 5 +--- .../drawable/qs_footer_actions_background.xml | 22 ++++++++++++++ packages/SystemUI/res/layout/qs_panel.xml | 4 +-- packages/SystemUI/res/values/dimens.xml | 9 +++++- .../systemui/qs/FooterActionsController.kt | 6 ---- .../android/systemui/qs/QSContainerImpl.java | 10 +++++-- .../src/com/android/systemui/qs/QSPanel.java | 12 ++++++-- .../systemui/qs/QSPanelController.java | 5 +++- .../NotificationsQSContainerController.kt | 12 +++++--- .../systemui/qs/QSPanelControllerTest.java | 5 +++- .../com/android/systemui/qs/QSPanelTest.kt | 14 +++++++++ .../NotificationQSContainerControllerTest.kt | 29 ++++++++++++------- 14 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 packages/SystemUI/res/drawable/qs_footer_actions_background.xml diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml index 95bdd8948c7cb..175e45c12afa8 100644 --- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml +++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml @@ -20,7 +20,11 @@ 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" > @@ -31,23 +35,24 @@ android:layout_weight="1" /> + @@ -56,19 +61,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/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/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 dba7290dba09c..fe418cf9d75ca 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/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 4aedbc983d9c2..92e3c3cc4106e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -29,7 +29,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 @@ -90,11 +89,6 @@ 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 powerMenuLite: View = view.findViewById(R.id.pm_lite) 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/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..03149e34a6896 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; @@ -104,7 +106,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; @@ -121,6 +123,7 @@ public class QSPanelController extends QSPanelControllerBase { mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController); mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); + view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER)); } @Override 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/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/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) } From d68c626be06be2770ff52dc0a2473f1d9b90f74f Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Mon, 7 Feb 2022 13:04:09 -0500 Subject: [PATCH 2/2] 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 --- .../res-keyguard/layout/fgs_footer.xml | 95 +++++++++++++++++++ .../layout/new_footer_actions.xml | 8 +- .../SystemUI/res/drawable/new_fgs_dot.xml | 22 +++++ .../qs_security_footer_background.xml | 4 +- packages/SystemUI/res/values/strings.xml | 3 + .../systemui/qs/FgsManagerController.kt | 7 ++ .../systemui/qs/FooterActionsController.kt | 54 ++++++++++- .../systemui/qs/QSFgsManagerFooter.java | 54 ++++++++++- .../systemui/qs/QSPanelController.java | 15 ++- .../android/systemui/qs/QSSecurityFooter.java | 82 ++++++++++------ .../qs/VisibilityChangedDispatcher.kt | 28 ++++++ .../systemui/qs/dagger/QSFragmentModule.java | 2 +- .../util/DualHeightHorizontalLinearLayout.kt | 17 +++- .../qs/FooterActionsControllerTest.kt | 7 +- .../systemui/qs/QSSecurityFooterTest.java | 79 ++++++++++++--- 15 files changed, 416 insertions(+), 61 deletions(-) create mode 100644 packages/SystemUI/res-keyguard/layout/fgs_footer.xml create mode 100644 packages/SystemUI/res/drawable/new_fgs_dot.xml create mode 100644 packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt 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 175e45c12afa8..4884df7f838e7 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" > - + /> + + + + \ 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 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/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e5cabb0ecac08..bdc9dbd8f6c86 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 eb3415639db60..d26c1c54ecb8d 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 92e3c3cc4106e..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 @@ -45,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 @@ -56,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, @@ -64,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, @@ -91,8 +96,13 @@ class FooterActionsController @Inject constructor( 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 @@ class FooterActionsController @Inject constructor( override fun onInit() { multiUserSwitchController.init() + fgsManagerFooterController.init() } private fun updateVisibility() { @@ -172,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) } @@ -195,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/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/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 03149e34a6896..418c4ae023ca0 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 @@ 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; @@ -116,13 +117,13 @@ 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)); } @@ -153,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(); @@ -195,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 776ee1021db28..816a3870eab0c 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 @@ 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/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/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);