diff options
| author | 2021-09-24 15:23:48 +0100 | |
|---|---|---|
| committer | 2021-10-06 14:17:36 +0100 | |
| commit | 626b14e46728a68f08f30c362d51137c0147a6d7 (patch) | |
| tree | a1021b567f9c02e4499294de4b16bef79e65ba5c | |
| parent | c4c480ab7e290a308827297606fb96cf7ada2f82 (diff) | |
Fixing bottom part of QS not getting any touches in split shade
The main issue was that QS and notifications container had padding of the size of taskbar. It was because taskbar is included in window insets and container doesn't check where insets come from. If insets are coming from taskbar only they should be ignored because taskbar should disappear when shade is expanded. But if they're coming from taskabar and navigation buttons are visible, they shouldn't be ignored as then taskbar always stays on the screen - that's assessed with checking if gesture navigation is available (if not, it means button navigation). Also because buttons are on the right side only and taskbar might or might not be visible, QS is just taking full height and notifications (that are on the right) have bigger spacing from the bottom.
Because this change involved using a few extra objects not available in NotificationsQuickSettingsContainer and I wanted to avoid doing everything in NotificationPanelViewController (it's big enough) and just passing results to NotificationsQuickSettingsContainer, I introduced controller for it: NotificationsQSContainerController. Now this controller has all the logic for determining bottom spacing. There can be a few more things that can be moved from NPVC to NotificationsQSContainerController but it's a start. Also now QS views and its children views instead of talking directly to container, they talk to controller via QSContainerController interface, which is a nice side-effect improvement.
Fixes: 194989891
Fixes: 200291264
Test: Home screen (so taskbar is hidden) -> go to split shade -> use anything that is close to the bottom of QS side (music widget, rearranging QS tiles, etc)
I'll add unit tests if the change overall looks good
Change-Id: I91ab8ec26dd2ef60562ffb9fdbc0e5739745ca0b
13 files changed, 515 insertions, 123 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 0424382a1b88..a03d84956ddc 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -16,7 +16,6 @@ package com.android.systemui.plugins.qs; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewGroup; import com.android.systemui.plugins.FragmentBase; import com.android.systemui.plugins.annotations.DependsOn; @@ -56,7 +55,7 @@ public interface QS extends FragmentBase { void setQsExpansion(float qsExpansionFraction, float headerTranslation); void setHeaderListening(boolean listening); void notifyCustomizeChanged(); - void setContainer(ViewGroup container); + void setContainerController(QSContainerController controller); void setExpandClickListener(OnClickListener onClickListener); View getHeader(); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt new file mode 100644 index 000000000000..8bf982d360a1 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt @@ -0,0 +1,9 @@ +package com.android.systemui.plugins.qs + +interface QSContainerController { + fun setCustomizerAnimating(animating: Boolean) + + fun setCustomizerShowing(showing: Boolean) + + fun setDetailShowing(showing: Boolean) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 8e436611e805..3fc4f504d007 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -26,7 +26,6 @@ import android.graphics.Point; import android.graphics.PointF; import android.util.AttributeSet; import android.view.View; -import android.view.WindowInsets; import android.widget.FrameLayout; import android.widget.ImageView; @@ -58,7 +57,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private int mSideMargins; private boolean mQsDisabled; private int mContentPadding = -1; - private int mNavBarInset = 0; private boolean mClippingEnabled; public QSContainerImpl(Context context, AttributeSet attrs) { @@ -95,24 +93,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom; - mQSPanelContainer.setPaddingRelative( - mQSPanelContainer.getPaddingStart(), - mQSPanelContainer.getPaddingTop(), - mQSPanelContainer.getPaddingEnd(), - mNavBarInset - ); - return super.onApplyWindowInsets(insets); - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the // bottom and footer are inside the screen. MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); - int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin + int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec); + int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin - getPaddingBottom(); int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin + layoutParams.rightMargin; @@ -122,11 +109,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); int width = mQSPanelContainer.getMeasuredWidth() + padding; super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY)); // QSCustomizer will always be the height of the screen, but do this after // other measuring to avoid changing the height of the QS. mQSCustomizer.measure(widthMeasureSpec, - MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 929927e5d4e4..58a942a05cbc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -46,8 +46,8 @@ import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; public class QSDetail extends LinearLayout { @@ -86,7 +86,7 @@ public class QSDetail extends LinearLayout { private boolean mSwitchState; private QSFooter mFooter; - private NotificationsQuickSettingsContainer mContainer; + private QSContainerController mQsContainerController; public QSDetail(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -120,8 +120,8 @@ public class QSDetail extends LinearLayout { mClipper = new QSDetailClipper(this); } - public void setContainer(NotificationsQuickSettingsContainer container) { - mContainer = container; + public void setContainerController(QSContainerController controller) { + mQsContainerController = controller; } /** */ @@ -262,8 +262,8 @@ public class QSDetail extends LinearLayout { } sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); animateDetailVisibleDiff(x, y, visibleDiff, listener); - if (mContainer != null) { - mContainer.setDetailShowing(showingDetail); + if (mQsContainerController != null) { + mQsContainerController.setDetailShowing(showingDetail); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 6fd81411dbc0..b8376da8a885 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -43,6 +43,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; @@ -50,7 +51,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.util.InjectionInflationController; @@ -336,11 +336,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void setContainer(ViewGroup container) { - if (container instanceof NotificationsQuickSettingsContainer) { - mQSCustomizerController.setContainer((NotificationsQuickSettingsContainer) container); - mQSDetail.setContainer((NotificationsQuickSettingsContainer) container); - } + public void setContainerController(QSContainerController controller) { + mQSCustomizerController.setContainerController(controller); + mQSDetail.setContainerController(controller); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index d33982c6e172..1a6d49070a2f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -33,9 +33,9 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.qs.QSDetailClipper; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; /** * Allows full-screen customization of QS, through show() and hide(). @@ -54,7 +54,7 @@ public class QSCustomizer extends LinearLayout { private boolean isShown; private final RecyclerView mRecyclerView; private boolean mCustomizing; - private NotificationsQuickSettingsContainer mNotifQsContainer; + private QSContainerController mQsContainerController; private QS mQs; private int mX; private int mY; @@ -103,8 +103,8 @@ public class QSCustomizer extends LinearLayout { lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown); } - public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) { - mNotifQsContainer = notificationsQsContainer; + public void setContainerController(QSContainerController controller) { + mQsContainerController = controller; } public void setQs(QS qs) { @@ -123,8 +123,8 @@ public class QSCustomizer extends LinearLayout { mOpening = true; setVisibility(View.VISIBLE); mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter)); - mNotifQsContainer.setCustomizerAnimating(true); - mNotifQsContainer.setCustomizerShowing(true); + mQsContainerController.setCustomizerAnimating(true); + mQsContainerController.setCustomizerShowing(true); } } @@ -136,8 +136,8 @@ public class QSCustomizer extends LinearLayout { mClipper.showBackground(); isShown = true; setCustomizing(true); - mNotifQsContainer.setCustomizerAnimating(false); - mNotifQsContainer.setCustomizerShowing(true); + mQsContainerController.setCustomizerAnimating(false); + mQsContainerController.setCustomizerShowing(true); } } @@ -154,8 +154,8 @@ public class QSCustomizer extends LinearLayout { } else { setVisibility(View.GONE); } - mNotifQsContainer.setCustomizerAnimating(animate); - mNotifQsContainer.setCustomizerShowing(false); + mQsContainerController.setCustomizerAnimating(animate); + mQsContainerController.setCustomizerShowing(false); } } @@ -193,7 +193,7 @@ public class QSCustomizer extends LinearLayout { setCustomizing(true); } mOpening = false; - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); mRecyclerView.setAdapter(mTileAdapter); } @@ -201,7 +201,7 @@ public class QSCustomizer extends LinearLayout { public void onAnimationCancel(Animator animation) { mOpening = false; mQs.notifyCustomizeChanged(); - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); } } @@ -211,7 +211,7 @@ public class QSCustomizer extends LinearLayout { if (!isShown) { setVisibility(View.GONE); } - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); } @Override @@ -219,7 +219,7 @@ public class QSCustomizer extends LinearLayout { if (!isShown) { setVisibility(View.GONE); } - mNotifQsContainer.setCustomizerAnimating(false); + mQsContainerController.setCustomizerAnimating(false); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index 49d18e62346a..618a429f6b51 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -35,13 +35,13 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -233,8 +233,8 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { } /** */ - public void setContainer(NotificationsQuickSettingsContainer container) { - mView.setContainer(container); + public void setContainerController(QSContainerController controller) { + mView.setContainerController(controller); } public boolean isShown() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 4862d1617837..ab0499bc0dde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -310,6 +310,7 @@ public class NotificationPanelViewController extends PanelViewController { private KeyguardStatusViewController mKeyguardStatusViewController; private LockIconViewController mLockIconViewController; private NotificationsQuickSettingsContainer mNotificationContainerParent; + private NotificationsQSContainerController mNotificationsQSContainerController; private boolean mAnimateNextPositionUpdate; private float mQuickQsOffsetHeight; private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @@ -653,6 +654,7 @@ public class NotificationPanelViewController extends PanelViewController { ConversationNotificationManager conversationNotificationManager, MediaHierarchyManager mediaHierarchyManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + NotificationsQSContainerController notificationsQSContainerController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, @@ -708,6 +710,8 @@ public class NotificationPanelViewController extends PanelViewController { mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; mMediaHierarchyManager = mediaHierarchyManager; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mNotificationsQSContainerController = notificationsQSContainerController; + mNotificationsQSContainerController.init(); mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mGroupManager = groupManager; mNotificationIconAreaController = notificationIconAreaController; @@ -1005,7 +1009,7 @@ public class NotificationPanelViewController extends PanelViewController { constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin); constraintSet.setMargin(R.id.qs_frame, TOP, topMargin); constraintSet.applyTo(mNotificationContainerParent); - mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade); + mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade); updateKeyguardStatusViewAlignment(/* animate= */false); @@ -2106,7 +2110,7 @@ public class NotificationPanelViewController extends PanelViewController { requestPanelHeightUpdate(); mFalsingCollector.setQsExpanded(expanded); mStatusBar.setQsExpanded(expanded); - mNotificationContainerParent.setQsExpanded(expanded); + mNotificationsQSContainerController.setQsExpanded(expanded); mPulseExpansionHandler.setQsExpanded(expanded); mKeyguardBypassController.setQSExpanded(expanded); mStatusBarKeyguardViewManager.setQsExpanded(expanded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt new file mode 100644 index 000000000000..34bb6d3e1a27 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -0,0 +1,141 @@ +package com.android.systemui.statusbar.phone + +import android.view.WindowInsets +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.plugins.qs.QS +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.shared.system.QuickStepContract +import com.android.systemui.util.ViewController +import java.util.function.Consumer +import javax.inject.Inject + +class NotificationsQSContainerController @Inject constructor( + view: NotificationsQuickSettingsContainer, + private val navigationModeController: NavigationModeController, + private val overviewProxyService: OverviewProxyService +) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { + + var qsExpanded = false + set(value) { + if (field != value) { + field = value + mView.invalidate() + } + } + var splitShadeEnabled = false + set(value) { + if (field != value) { + field = value + // in case device configuration changed while showing QS details/customizer + updateBottomSpacing() + } + } + + private var isQSDetailShowing = false + private var isQSCustomizing = false + private var isQSCustomizerAnimating = false + + private var notificationsBottomMargin = 0 + private var bottomStableInsets = 0 + private var bottomCutoutInsets = 0 + + private var isGestureNavigation = true + private var taskbarVisible = false + private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener { + override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) { + taskbarVisible = visible + } + } + private val windowInsetsListener: Consumer<WindowInsets> = Consumer { insets -> + // when taskbar is visible, stableInsetBottom will include its height + bottomStableInsets = insets.stableInsetBottom + bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 + updateBottomSpacing() + } + + override fun onInit() { + val currentMode: Int = navigationModeController.addListener { mode: Int -> + isGestureNavigation = QuickStepContract.isGesturalMode(mode) + } + isGestureNavigation = QuickStepContract.isGesturalMode(currentMode) + } + + public override fun onViewAttached() { + notificationsBottomMargin = mView.defaultNotificationsMarginBottom + overviewProxyService.addCallback(taskbarVisibilityListener) + mView.setInsetsChangedListener(windowInsetsListener) + mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } + } + + override fun onViewDetached() { + overviewProxyService.removeCallback(taskbarVisibilityListener) + mView.removeOnInsetsChangedListener() + mView.removeQSFragmentAttachedListener() + } + + override fun setCustomizerAnimating(animating: Boolean) { + if (isQSCustomizerAnimating != animating) { + isQSCustomizerAnimating = animating + mView.invalidate() + } + } + + override fun setCustomizerShowing(showing: Boolean) { + isQSCustomizing = showing + updateBottomSpacing() + } + + override fun setDetailShowing(showing: Boolean) { + isQSDetailShowing = showing + updateBottomSpacing() + } + + private fun updateBottomSpacing() { + val (containerPadding, notificationsMargin) = calculateBottomSpacing() + var qsScrollPaddingBottom = 0 + if (!(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 + } + mView.setPadding(0, 0, 0, containerPadding) + mView.setNotificationsMarginBottom(notificationsMargin) + mView.setQSScrollPaddingBottom(qsScrollPaddingBottom) + } + + private fun calculateBottomSpacing(): Pair<Int, Int> { + val containerPadding: Int + var stackScrollMargin = notificationsBottomMargin + if (splitShadeEnabled) { + if (isGestureNavigation) { + // only default cutout padding, taskbar always hides + containerPadding = bottomCutoutInsets + } else if (taskbarVisible) { + // navigation buttons + visible taskbar means we're NOT on homescreen + containerPadding = bottomStableInsets + } else { + // navigation buttons + hidden taskbar means we're on homescreen + containerPadding = 0 + // we need extra margin for notifications as navigation buttons are below them + stackScrollMargin = bottomStableInsets + notificationsBottomMargin + } + } else { + if (isQSCustomizing || isQSDetailShowing) { + // Clear out bottom paddings/margins so the qs customization can be full height. + containerPadding = 0 + stackScrollMargin = 0 + } else if (isGestureNavigation) { + containerPadding = bottomCutoutInsets + } else if (taskbarVisible) { + containerPadding = bottomStableInsets + } else { + containerPadding = 0 + } + } + return containerPadding to stackScrollMargin + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index 68e28cdba975..9210a8b5db80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import java.util.ArrayList; import java.util.Comparator; +import java.util.function.Consumer; /** * The container with notification stack scroller and quick settings inside. @@ -43,17 +44,15 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private View mQsFrame; private View mStackScroller; private View mKeyguardStatusBar; - private boolean mQsExpanded; - private boolean mCustomizerAnimating; - private boolean mCustomizing; - private boolean mDetailShowing; - private int mBottomPadding; private int mStackScrollerMargin; private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>(); private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>(); private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild); - private boolean mSplitShadeEnabled; + private Consumer<WindowInsets> mInsetsChangedListener = insets -> {}; + private Consumer<QS> mQSFragmentAttachedListener = qs -> {}; + private QS mQs; + private View mQSScrollView; public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -69,6 +68,59 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override + public void onFragmentViewCreated(String tag, Fragment fragment) { + mQs = (QS) fragment; + mQSFragmentAttachedListener.accept(mQs); + mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view); + } + + @Override + public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) { + invalidate(); + } + + public void setNotificationsMarginBottom(int margin) { + LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams(); + params.bottomMargin = margin; + mStackScroller.setLayoutParams(params); + } + + public void setQSScrollPaddingBottom(int paddingBottom) { + if (mQSScrollView != null) { + mQSScrollView.setPaddingRelative( + mQSScrollView.getPaddingLeft(), + mQSScrollView.getPaddingTop(), + mQSScrollView.getPaddingRight(), + paddingBottom + ); + } + } + + public int getDefaultNotificationsMarginBottom() { + return mStackScrollerMargin; + } + + public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) { + mInsetsChangedListener = onInsetsChangedListener; + } + + public void removeOnInsetsChangedListener() { + mInsetsChangedListener = insets -> {}; + } + + public void setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener) { + mQSFragmentAttachedListener = qsFragmentAttachedListener; + // listener might be attached after fragment is attached + if (mQs != null) { + mQSFragmentAttachedListener.accept(mQs); + } + } + + public void removeQSFragmentAttachedListener() { + mQSFragmentAttachedListener = qs -> {}; + } + + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); FragmentHostManager.get(this).addTagListener(QS.TAG, this); @@ -82,8 +134,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mBottomPadding = insets.getStableInsetBottom(); - setPadding(0, 0, 0, mBottomPadding); + mInsetsChangedListener.accept(insets); return insets; } @@ -119,66 +170,4 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } } - @Override - public void onFragmentViewCreated(String tag, Fragment fragment) { - QS container = (QS) fragment; - container.setContainer(this); - } - - public void setQsExpanded(boolean expanded) { - if (mQsExpanded != expanded) { - mQsExpanded = expanded; - invalidate(); - } - } - - public void setCustomizerAnimating(boolean isAnimating) { - if (mCustomizerAnimating != isAnimating) { - mCustomizerAnimating = isAnimating; - invalidate(); - } - } - - public void setCustomizerShowing(boolean isShowing) { - mCustomizing = isShowing; - updateBottomMargin(); - } - - public void setDetailShowing(boolean isShowing) { - mDetailShowing = isShowing; - updateBottomMargin(); - } - - /** - * Sets if split shade is enabled and adjusts margins/paddings depending on QS details and - * customizer state - */ - public void setSplitShadeEnabled(boolean splitShadeEnabled) { - mSplitShadeEnabled = splitShadeEnabled; - // in case device was rotated while showing QS details/customizer - updateBottomMargin(); - } - - private void updateBottomMargin() { - // in split shade, QS state changes should not influence notifications panel - if (!mSplitShadeEnabled && (mCustomizing || mDetailShowing)) { - // Clear out bottom paddings/margins so the qs customization can be full height. - setPadding(0, 0, 0, 0); - setBottomMargin(mStackScroller, 0); - } else { - setPadding(0, 0, 0, mBottomPadding); - setBottomMargin(mStackScroller, mStackScrollerMargin); - } - } - - private void setBottomMargin(View v, int bottomMargin) { - LayoutParams params = (LayoutParams) v.getLayoutParams(); - params.bottomMargin = bottomMargin; - v.setLayoutParams(params); - } - - @Override - public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) { - invalidate(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index ecf3b86561b9..2791678c3b4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -29,9 +29,9 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.NotificationPanelView; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; +import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.phone.TapAgainView; import com.android.systemui.util.InjectionInflationController; @@ -67,8 +67,8 @@ public abstract class StatusBarViewModule { @Provides @StatusBarComponent.StatusBarScope public static NotificationStackScrollLayout providesNotificationStackScrollLayout( - NotificationStackScrollLayoutController notificationStackScrollLayoutController) { - return notificationStackScrollLayoutController.getView(); + NotificationShadeWindowView notificationShadeWindowView) { + return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller); } /** */ @@ -149,4 +149,12 @@ public abstract class StatusBarViewModule { public static TapAgainView getTapAgainView(NotificationPanelView npv) { return npv.getTapAgainView(); } + + /** */ + @Provides + @StatusBarComponent.StatusBarScope + public static NotificationsQuickSettingsContainer getNotificationsQuickSettingsContainer( + NotificationShadeWindowView notificationShadeWindowView) { + return notificationShadeWindowView.findViewById(R.id.notification_container_parent); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index f7423bb7951d..253077f4ab08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -300,6 +300,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private LockscreenGestureLogger mLockscreenGestureLogger; @Mock private DumpManager mDumpManager; + @Mock + private NotificationsQSContainerController mNotificationsQSContainerController; private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -414,6 +416,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, mConversationNotificationManager, mMediaHiearchyManager, mStatusBarKeyguardViewManager, + mNotificationsQSContainerController, mNotificationStackScrollLayoutController, mKeyguardStatusViewComponentFactory, mKeyguardQsUserSwitchComponentFactory, 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 new file mode 100644 index 000000000000..337e64592750 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -0,0 +1,254 @@ +package com.android.systemui.statusbar.phone + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.WindowInsets +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener +import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.RETURNS_DEEP_STUBS +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.function.Consumer +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class NotificationQSContainerControllerTest : SysuiTestCase() { + + companion object { + const val STABLE_INSET_BOTTOM = 100 + const val CUTOUT_HEIGHT = 50 + const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL + const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON + const val NOTIFICATIONS_MARGIN = 50 + } + + @Mock + private lateinit var navigationModeController: NavigationModeController + @Mock + private lateinit var overviewProxyService: OverviewProxyService + @Mock + private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer + @Captor + lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> + @Captor + lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener> + @Captor + lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>> + + private lateinit var notificationsQSContainerController: NotificationsQSContainerController + private lateinit var navigationModeCallback: ModeChangedListener + private lateinit var taskbarVisibilityCallback: OverviewProxyListener + private lateinit var windowInsetsCallback: Consumer<WindowInsets> + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + notificationsQSContainerController = NotificationsQSContainerController( + notificationsQSContainer, + navigationModeController, + overviewProxyService + ) + whenever(notificationsQSContainer.defaultNotificationsMarginBottom) + .thenReturn(NOTIFICATIONS_MARGIN) + whenever(navigationModeController.addListener(navigationModeCaptor.capture())) + .thenReturn(GESTURES_NAVIGATION) + doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture()) + doNothing().`when`(notificationsQSContainer) + .setInsetsChangedListener(windowInsetsCallbackCaptor.capture()) + + notificationsQSContainerController.init() + notificationsQSContainerController.onViewAttached() + + navigationModeCallback = navigationModeCaptor.value + taskbarVisibilityCallback = taskbarVisibilityCaptor.value + windowInsetsCallback = windowInsetsCallbackCaptor.value + } + + @Test + fun testTaskbarVisibleInSplitShade() { + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = true, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded + expectedNotificationsMargin = NOTIFICATIONS_MARGIN) + + given(taskbarVisible = true, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = STABLE_INSET_BOTTOM, + expectedNotificationsMargin = NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarNotVisibleInSplitShade() { + // when taskbar is not visible, it means we're on the home screen + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + 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) + } + + @Test + fun testTaskbarNotVisibleInSplitShadeWithCutout() { + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withCutout()) + then(expectedContainerPadding = CUTOUT_HEIGHT) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withCutout().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarVisibleInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + given(taskbarVisible = true, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = true, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = STABLE_INSET_BOTTOM) + } + + @Test + fun testTaskbarNotVisibleInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withCutout().withStableBottom()) + then(expectedContainerPadding = CUTOUT_HEIGHT) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedQsPadding = STABLE_INSET_BOTTOM) + } + + @Test + fun testCustomizingInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + notificationsQSContainerController.setCustomizerShowing(true) + // always sets spacings to 0 + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + } + + @Test + fun testDetailShowingInSinglePaneShade() { + notificationsQSContainerController.splitShadeEnabled = false + notificationsQSContainerController.setDetailShowing(true) + // always sets spacings to 0 + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + } + + @Test + fun testDetailShowingInSplitShade() { + notificationsQSContainerController.splitShadeEnabled = true + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + notificationsQSContainerController.setDetailShowing(true) + // should not influence spacing + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0) + } + + private fun given( + taskbarVisible: Boolean, + navigationMode: Int, + insets: WindowInsets + ) { + Mockito.clearInvocations(notificationsQSContainer) + taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false) + navigationModeCallback.onNavigationModeChanged(navigationMode) + windowInsetsCallback.accept(insets) + } + + fun then( + expectedContainerPadding: Int, + expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN, + expectedQsPadding: Int = 0 + ) { + verify(notificationsQSContainer) + .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding)) + verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin) + verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding) + Mockito.clearInvocations(notificationsQSContainer) + } + + private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS) + + private fun emptyInsets() = mock(WindowInsets::class.java) + + private fun WindowInsets.withCutout(): WindowInsets { + whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT) + return this + } + + private fun WindowInsets.withStableBottom(): WindowInsets { + whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM) + return this + } +}
\ No newline at end of file |