summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java3
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetail.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt141
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt254
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