Merge "Fix NotificationStack size calculation" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-03-18 16:31:33 +00:00
committed by Android (Google) Code Review
8 changed files with 622 additions and 191 deletions

View File

@@ -217,6 +217,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
private ArrayList<View> mSwipedOutViews = new ArrayList<>();
private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
private boolean mAnimationsEnabled;
private boolean mChangePositionInProgress;
@@ -415,6 +416,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private NotificationShelf mShelf;
private int mMaxDisplayedNotifications = -1;
private float mKeyguardBottomPadding = -1;
private float mKeyguardNotificationAvailableSpace = -1;
@VisibleForTesting int mStatusBarHeight;
private int mMinInteractionHeight;
private final Rect mClipRect = new Rect();
@@ -757,31 +759,34 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} else {
mDebugTextUsedYPositions.clear();
}
int y = mTopPadding;
drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding = "+y);
int y = 0;
drawDebugInfo(canvas, y, Color.RED, /* label= */ "y = " + y);
y = mTopPadding;
drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding = " + y);
y = getLayoutHeight();
drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = "+y);
drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = " + y);
y = (int) mMaxLayoutHeight;
drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight = "+y);
drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight = " + y);
if (mKeyguardBottomPadding >= 0) {
y = getHeight() - (int) mKeyguardBottomPadding;
drawDebugInfo(canvas, y, Color.GRAY,
/* label= */ "getHeight() - mKeyguardBottomPadding = "+y);
/* label= */ "getHeight() - mKeyguardBottomPadding = " + y);
}
y = getHeight() - getEmptyBottomMargin();
drawDebugInfo(canvas, y, Color.GREEN,
/* label= */ "getHeight() - getEmptyBottomMargin() = "+y);
/* label= */ "getHeight() - getEmptyBottomMargin() = " + y);
y = (int) (mAmbientState.getStackY());
drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY() = "+y);
drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY() = " + y);
y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
drawDebugInfo(canvas, y, Color.BLUE,
/* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = "+y);
/* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = " + y);
y = (int) mAmbientState.getStackY() + mContentHeight;
drawDebugInfo(canvas, y, Color.MAGENTA,
@@ -790,6 +795,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
y = (int) mAmbientState.getStackY() + mIntrinsicContentHeight;
drawDebugInfo(canvas, y, Color.YELLOW,
/* label= */ "mAmbientState.getStackY() + mIntrinsicContentHeight = " + y);
y = (int) (mAmbientState.getStackY() + mKeyguardNotificationAvailableSpace);
drawDebugInfo(canvas, y, Color.RED, /* label= */
"mAmbientState.getStackY() + mKeyguardNotificationAvailableSpace = " + y);
}
private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
@@ -956,13 +965,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void reinitView() {
initView(getContext(), mSwipeHelper);
initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
void initView(Context context, NotificationSwipeHelper swipeHelper) {
void initView(Context context, NotificationSwipeHelper swipeHelper,
NotificationStackSizeCalculator notificationStackSizeCalculator) {
mScroller = new OverScroller(getContext());
mSwipeHelper = swipeHelper;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setClipChildren(false);
@@ -2248,48 +2259,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void updateContentHeight() {
final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
int height = (int) scrimTopPadding;
float previousPaddingRequest = mPaddingBetweenElements;
int numShownItems = 0;
int numShownNotifs = 0;
boolean finish = false;
int maxDisplayedNotifications = mMaxDisplayedNotifications;
ExpandableView previousView = null;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView expandableView = (ExpandableView) getChildAt(i);
boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
if (expandableView.getVisibility() != View.GONE
&& !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
boolean limitReached = maxDisplayedNotifications != -1
&& numShownNotifs >= maxDisplayedNotifications;
final float viewHeight;
if (limitReached) {
viewHeight = mShelf.getIntrinsicHeight();
finish = true;
} else {
viewHeight = expandableView.getIntrinsicHeight();
}
if (height != 0) {
height += mPaddingBetweenElements;
}
float gapHeight = calculateGapHeight(previousView, expandableView, numShownNotifs);
height += gapHeight;
height += viewHeight;
numShownItems++;
if (viewHeight > 0 || !(expandableView instanceof MediaContainerView)) {
// Only count the media as a notification if it has a positive height.
numShownNotifs++;
}
previousView = expandableView;
if (finish) {
break;
}
}
}
final int height =
(int) scrimTopPadding + (int) mNotificationStackSizeCalculator.computeHeight(
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
mShelf != null ? mShelf.getIntrinsicHeight() : 0);
mIntrinsicContentHeight = height;
// The topPadding can be bigger than the regular padding when qs is expanded, in that
@@ -4932,6 +4905,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mKeyguardBottomPadding = keyguardBottomPadding;
}
/**
* For debugging only. Enables to draw a line related to the available size for notifications in
* keyguard.
*/
public void setKeyguardAvailableSpaceForDebug(float keyguardNotificationAvailableSpace) {
mKeyguardNotificationAvailableSpace = keyguardNotificationAvailableSpace;
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
mShouldShowShelfOnly = shouldShowShelfOnly;

View File

@@ -181,6 +181,7 @@ public class NotificationStackScrollLayoutController {
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
private final NotificationStackScrollLogger mLogger;
@@ -307,6 +308,7 @@ public class NotificationStackScrollLayoutController {
R.dimen.lockscreen_shade_notification_movement);
mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance);
mNotificationStackSizeCalculator.updateResources();
}
private final StatusBarStateController.StateListener mStateListener =
@@ -662,7 +664,8 @@ public class NotificationStackScrollLayoutController {
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger) {
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
@@ -688,6 +691,7 @@ public class NotificationStackScrollLayoutController {
mCentralSurfaces = centralSurfaces;
mScrimController = scrimController;
mJankMonitor = jankMonitor;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
groupManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
@@ -758,7 +762,7 @@ public class NotificationStackScrollLayoutController {
});
}
mView.initView(mView.getContext(), mSwipeHelper);
mView.initView(mView.getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
@@ -907,6 +911,13 @@ public class NotificationStackScrollLayoutController {
return mView.getTop();
}
/**
* @return the bottom of the view.
*/
public int getBottom() {
return mView.getBottom();
}
public float getTranslationX() {
return mView.getTranslationX();
}
@@ -1296,10 +1307,16 @@ public class NotificationStackScrollLayoutController {
* appear on the keyguard.
* Setting a negative number will disable rendering this line.
*/
public void setKeyguardBottomPadding(float keyguardBottomPadding) {
public void setKeyguardBottomPaddingForDebug(float keyguardBottomPadding) {
mView.setKeyguardBottomPadding(keyguardBottomPadding);
}
/** For debugging only. */
public void mKeyguardNotificationAvailableSpaceForDebug(
float keyguardNotificationAvailableSpace) {
mView.setKeyguardAvailableSpaceForDebug(keyguardNotificationAvailableSpace);
}
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,

View File

@@ -0,0 +1,261 @@
/*
* 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.statusbar.notification.stack
import android.content.res.Resources
import android.util.Log
import android.view.View.GONE
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.children
import javax.inject.Inject
import kotlin.math.max
import kotlin.properties.Delegates.notNull
private const val TAG = "NotificationStackSizeCalculator"
private const val DEBUG = false
/** Calculates number of notifications to display and the height of the notification stack. */
@SysUISingleton
class NotificationStackSizeCalculator
@Inject
constructor(
private val groupManager: NotificationGroupManagerLegacy,
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val statusBarStateController: SysuiStatusBarStateController,
@Main private val resources: Resources
) {
/**
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
* (considering the overflow shelf is not displayed in this case), then all notifications are
* shown.
*/
private var maxKeyguardNotifications by notNull<Int>()
/**
* Minimum space between two notifications. There might be more space, see [calculateGapHeight].
*/
private var notificationPadding by notNull<Int>()
init {
updateResources()
}
/**
* Given the [availableSpace] constraint, calculates how many notification to show.
*
* This number is only valid in keyguard.
*
* @param availableSpace space for notifications. This doesn't include the space for the shelf.
*/
fun computeMaxKeyguardNotifications(
stack: NotificationStackScrollLayout,
availableSpace: Float,
shelfHeight: Float
): Int {
log {
"computeMaxKeyguardNotifications(" +
"availableSpace=$availableSpace shelfHeight=$shelfHeight)"
}
val children: Sequence<ExpandableView> = stack.childrenSequence
var remainingSpace: Float = availableSpace
var count = 0
var previous: ExpandableView? = null
val onLockscreen = true
val showableRows = children.filter { it.isShowable(onLockscreen) }
val showableRowsCount = showableRows.count()
showableRows.forEachIndexed { i, current ->
val spaceNeeded = current.spaceNeeded(count, previous, stack, onLockscreen)
previous = current
log { "\ti=$i spaceNeeded=$spaceNeeded remainingSpace=$remainingSpace" }
if (remainingSpace - spaceNeeded >= 0 && count < maxKeyguardNotifications) {
count += 1
remainingSpace -= spaceNeeded
} else if (remainingSpace - spaceNeeded > -shelfHeight && i == showableRowsCount - 1) {
log { "Showing all notifications. Shelf is not be needed." }
// If this is the last one, and it fits using the space shelf would use, then we can
// display it, as the shelf will not be needed (as all notifications are shown).
return count + 1
} else {
log {
"No more fit. Returning $count. Space used: ${availableSpace - remainingSpace}"
}
return count
}
}
log { "All fit. Returning $count" }
return count
}
/**
* Given the [maxNotifications] constraint, calculates the height of the
* [NotificationStackScrollLayout]. This might or might not be in keyguard.
*
* @param stack stack containing notifications as children.
* @param maxNotifications Maximum number of notifications. When reached, the others will go
* into the shelf.
* @param shelfHeight height of the shelf. It might be zero.
*
* @return height of the stack, including shelf height, if needed.
*/
fun computeHeight(
stack: NotificationStackScrollLayout,
maxNotifications: Int,
shelfHeight: Float
): Float {
val children: Sequence<ExpandableView> = stack.childrenSequence
val maxNotificationsArg = infiniteIfNegative(maxNotifications)
var height = 0f
var previous: ExpandableView? = null
var count = 0
val onLockscreen = onLockscreen()
log { "computeHeight(maxNotification=$maxNotifications, shelf=$shelfHeight" }
children.filter { it.isShowable(onLockscreen) }.forEach { current ->
if (count < maxNotificationsArg) {
val spaceNeeded = current.spaceNeeded(count, previous, stack, onLockscreen)
log { "\ti=$count spaceNeeded=$spaceNeeded" }
height += spaceNeeded
count += 1
} else {
height += shelfHeight
log { "returning height with shelf -> $height" }
return height
}
previous = current
}
log { "Returning height without shelf -> $height" }
return height
}
fun updateResources() {
maxKeyguardNotifications =
infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
notificationPadding =
max(1, resources.getDimensionPixelSize(R.dimen.notification_divider_height))
}
private val NotificationStackScrollLayout.childrenSequence: Sequence<ExpandableView>
get() = children.map { it as ExpandableView }
private fun onLockscreen() = statusBarStateController.state == KEYGUARD
private fun ExpandableView.spaceNeeded(
visibleIndex: Int,
previousView: ExpandableView?,
stack: NotificationStackScrollLayout,
onLockscreen: Boolean
): Float {
assert(isShowable(onLockscreen))
var size =
if (onLockscreen) {
getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
} else {
intrinsicHeight.toFloat()
}
if (visibleIndex != 0) {
size += notificationPadding
}
size += calculateGapHeight(stack, previousView, visibleIndex)
return size
}
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
if (visibility == GONE || hasNoContentHeight()) return false
if (onLockscreen) {
when (this) {
is ExpandableNotificationRow -> {
if (isSummaryOfSuppressedGroup() || !canShowViewOnLockscreen() || isRemoved) {
return false
}
}
is MediaContainerView -> if (intrinsicHeight == 0) return false
else -> return false
}
}
return true
}
private fun ExpandableView.calculateGapHeight(
stack: NotificationStackScrollLayout,
previous: ExpandableView?,
visibleIndex: Int
) = stack.calculateGapHeight(previous, /* current= */ this, visibleIndex)
private fun ExpandableNotificationRow.isSummaryOfSuppressedGroup() =
groupManager.isSummaryOfSuppressedGroup(entry.sbn)
/**
* Can a view be shown on the lockscreen when calculating the number of allowed notifications to
* show?
*
* @return `true` if it can be shown.
*/
private fun ExpandableView.canShowViewOnLockscreen(): Boolean {
if (hasNoContentHeight()) {
return false
}
if (this is ExpandableNotificationRow && !canShowRowOnLockscreen()) {
return false
} else if (visibility == GONE) {
return false
}
return true
}
/**
* Can a row be shown on the lockscreen when calculating the number of allowed notifications to
* show?
*
* @return true if it can be shown
*/
private fun ExpandableNotificationRow.canShowRowOnLockscreen(): Boolean {
if (isSummaryOfSuppressedGroup()) {
return false
}
if (!lockscreenUserManager.shouldShowOnKeyguard(entry)) {
return false
}
return !isRemoved
}
private fun log(s: () -> String) {
if (DEBUG) {
Log.d(TAG, s())
}
}
/** Returns infinite when [v] is negative. Useful in case a resource doesn't limit when -1. */
private fun infiniteIfNegative(v: Int): Int =
if (v < 0) {
Int.MAX_VALUE
} else {
v
}
}

View File

@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.View.GONE;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
@@ -144,7 +143,6 @@ import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -165,17 +163,16 @@ import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.MediaContainerView;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -312,9 +309,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final ControlsComponent mControlsComponent;
private final NotificationRemoteInputManager mRemoteInputManager;
// Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
// If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
private final int mMaxKeyguardNotifications;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
private final SplitShadeHeaderController mSplitShadeHeaderController;
@@ -323,6 +317,8 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mShouldUseSplitNotificationShade;
// The bottom padding reserved for elements of the keyguard measuring notifications
private float mKeyguardNotificationBottomPadding;
// Space available for notifications.
private float mKeyguardNotificationAvailableSpace;
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
@@ -446,8 +442,6 @@ public class NotificationPanelViewController extends PanelViewController {
setHeadsUpAnimatingAway(false);
updatePanelExpansionAndVisibility();
};
// TODO (b/162832756): once migrated to the new pipeline, delete legacy group manager
private NotificationGroupManagerLegacy mGroupManager;
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
private int mAmbientIndicationBottomPadding;
@@ -509,7 +503,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final NotificationEntryManager mEntryManager;
private final CommandQueue mCommandQueue;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final UserManager mUserManager;
private final MediaDataManager mMediaDataManager;
private final SysUiState mSysUiState;
@@ -651,6 +644,7 @@ public class NotificationPanelViewController extends PanelViewController {
mNotificationPanelUnfoldAnimationController;
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
@Override
@@ -695,7 +689,6 @@ public class NotificationPanelViewController extends PanelViewController {
DynamicPrivacyController dynamicPrivacyController,
KeyguardBypassController bypassController, FalsingManager falsingManager,
FalsingCollector falsingCollector,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
@@ -721,7 +714,6 @@ public class NotificationPanelViewController extends PanelViewController {
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
AuthController authController,
ScrimController scrimController,
@@ -753,7 +745,8 @@ public class NotificationPanelViewController extends PanelViewController {
SysUiState sysUiState,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationListContainer notificationListContainer,
PanelEventsEmitter panelEventsEmitter) {
PanelEventsEmitter panelEventsEmitter,
NotificationStackSizeCalculator notificationStackSizeCalculator) {
super(view,
falsingManager,
dozeLog,
@@ -785,9 +778,9 @@ public class NotificationPanelViewController extends PanelViewController {
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
@@ -849,7 +842,6 @@ public class NotificationPanelViewController extends PanelViewController {
});
mBottomAreaShadeAlphaAnimator.setDuration(160);
mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mLockscreenUserManager = notificationLockscreenUserManager;
mEntryManager = notificationEntryManager;
mConversationNotificationManager = conversationNotificationManager;
mAuthController = authController;
@@ -874,7 +866,6 @@ public class NotificationPanelViewController extends PanelViewController {
mView.getOverlay().add(new DebugDrawable());
}
mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
@@ -1238,12 +1229,14 @@ public class NotificationPanelViewController extends PanelViewController {
if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
mMaxAllowedKeyguardNotifications);
mNotificationStackScrollLayoutController.setKeyguardBottomPadding(
mNotificationStackScrollLayoutController.setKeyguardBottomPaddingForDebug(
mKeyguardNotificationBottomPadding);
mNotificationStackScrollLayoutController.mKeyguardNotificationAvailableSpaceForDebug(
mKeyguardNotificationAvailableSpace);
} else {
// no max when not on the keyguard
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
mNotificationStackScrollLayoutController.setKeyguardBottomPadding(-1f);
mNotificationStackScrollLayoutController.setKeyguardBottomPaddingForDebug(-1f);
}
}
@@ -1454,127 +1447,38 @@ public class NotificationPanelViewController extends PanelViewController {
* @return the maximum keyguard notifications that can fit on the screen
*/
private int computeMaxKeyguardNotifications() {
float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
int notificationPadding = Math.max(
1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
float shelfSize =
float topPadding = mNotificationStackScrollLayoutController.getTopPadding();
float shelfHeight =
mNotificationShelfController.getVisibility() == View.GONE
? 0
: mNotificationShelfController.getIntrinsicHeight() + notificationPadding;
// Padding to add to the bottom of the stack to keep a minimum distance from the top of
// the lock icon.
float lockIconPadding = 0;
if (mLockIconViewController.getTop() != 0) {
lockIconPadding = mCentralSurfaces.getDisplayHeight() - mLockIconViewController.getTop()
+ mResources.getDimensionPixelSize(R.dimen.min_lock_icon_padding);
final float lockIconTopWithPadding = mLockIconViewController.getTop()
- mResources.getDimensionPixelSize(R.dimen.min_lock_icon_padding);
lockIconPadding = mNotificationStackScrollLayoutController.getBottom()
- lockIconTopWithPadding;
}
float bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
bottomPadding = Math.max(lockIconPadding, bottomPadding);
float bottomPadding = Math.max(lockIconPadding,
Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding));
mKeyguardNotificationBottomPadding = bottomPadding;
float availableSpace =
mNotificationStackScrollLayoutController.getHeight()
- minPadding
- shelfSize
- topPadding
- shelfHeight
- bottomPadding;
mKeyguardNotificationAvailableSpace = availableSpace;
int count = 0;
ExpandableView previousView = null;
for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
ExpandableView child = mNotificationStackScrollLayoutController.getChildAt(i);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
boolean suppressedSummary = mGroupManager != null
&& mGroupManager.isSummaryOfSuppressedGroup(row.getEntry().getSbn());
if (suppressedSummary) {
continue;
}
if (!canShowViewOnLockscreen(child)) {
continue;
}
if (row.isRemoved()) {
continue;
}
} else if (child instanceof MediaContainerView) {
if (child.getVisibility() == GONE) {
continue;
}
if (child.getIntrinsicHeight() == 0) {
continue;
}
} else {
continue;
}
availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */);
availableSpace -= count == 0 ? 0 : notificationPadding;
availableSpace -= mNotificationStackScrollLayoutController
.calculateGapHeight(previousView, child, count);
previousView = child;
if (availableSpace >= 0
&& (mMaxKeyguardNotifications == -1 || count < mMaxKeyguardNotifications)) {
count++;
} else if (availableSpace > -shelfSize) {
// if we are exactly the last view, then we can show us still!
int childCount = mNotificationStackScrollLayoutController.getChildCount();
for (int j = i + 1; j < childCount; j++) {
ExpandableView view = mNotificationStackScrollLayoutController.getChildAt(j);
if (view instanceof ExpandableNotificationRow
&& canShowViewOnLockscreen(view)) {
return count;
}
}
count++;
return count;
} else {
return count;
}
}
return count;
}
/**
* Can a view be shown on the lockscreen when calculating the number of allowed notifications
* to show?
*
* @param child the view in question
* @return true if it can be shown
*/
private boolean canShowViewOnLockscreen(ExpandableView child) {
if (child.hasNoContentHeight()) {
return false;
}
if (child instanceof ExpandableNotificationRow &&
!canShowRowOnLockscreen((ExpandableNotificationRow) child)) {
return false;
} else if (child.getVisibility() == GONE) {
// ENRs can be gone and count because their visibility is only set after
// this calculation, but all other views should be up to date
return false;
}
return true;
}
/**
* Can a row be shown on the lockscreen when calculating the number of allowed notifications
* to show?
*
* @param row the row in question
* @return true if it can be shown
*/
private boolean canShowRowOnLockscreen(ExpandableNotificationRow row) {
boolean suppressedSummary =
mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
row.getEntry().getSbn());
if (suppressedSummary) {
return false;
}
if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
return false;
}
if (row.isRemoved()) {
return false;
}
return true;
return mNotificationStackSizeCalculator.computeMaxKeyguardNotifications(
mNotificationStackScrollLayoutController.getView(), availableSpace,
shelfHeight);
}
private void updateClock() {
@@ -4883,6 +4787,8 @@ public class NotificationPanelViewController extends PanelViewController {
"calculateNotificationsTopPadding()");
drawDebugInfo(canvas, mClockPositionResult.clockY, Color.GRAY,
"mClockPositionResult.clockY");
drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
"mLockIconViewController.getTop()");
mDebugPaint.setColor(Color.CYAN);
canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),

View File

@@ -134,6 +134,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -186,7 +187,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mShadeController,
mJankMonitor,
mStackLogger,
mLogger
mLogger,
mNotificationStackSizeCalculator
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);

View File

@@ -103,6 +103,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
@Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock private NotificationShelf mNotificationShelf;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Before
@UiThreadTest
@@ -138,7 +139,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
// holds a copy of the CUT's instances of these KeyguardBypassController, so they still
// refer to the CUT's member variables, not the spy's member variables.
mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null);
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper);
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelfController(notificationShelfController);
mStackScroller.setCentralSurfaces(mCentralSurfaces);

View File

@@ -0,0 +1,266 @@
/*
* 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.statusbar.notification.stack
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.view.View.VISIBLE
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class NotificationStackSizeCalculatorTest : SysuiTestCase() {
@Mock private lateinit var groupManager: NotificationGroupManagerLegacy
@Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var stackLayout: NotificationStackScrollLayout
private val testableResources = mContext.getOrCreateTestableResources()
private lateinit var sizeCalculator: NotificationStackSizeCalculator
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(stackLayout.calculateGapHeight(nullable(), nullable(), any()))
.thenReturn(GAP_HEIGHT)
whenever(groupManager.isSummaryOfSuppressedGroup(any())).thenReturn(false)
with(testableResources) {
addOverride(R.integer.keyguard_max_notification_count, -1)
addOverride(R.dimen.notification_divider_height, NOTIFICATION_PADDING.toInt())
}
sizeCalculator =
NotificationStackSizeCalculator(
groupManager = groupManager,
lockscreenUserManager = notificationLockscreenUserManager,
statusBarStateController = sysuiStatusBarStateController,
testableResources.resources)
}
@Test
fun computeMaxKeyguardNotifications_zeroSpace_returnZero() {
val rows = listOf(createMockRow(height = ROW_HEIGHT, visibleOnLockscreen = true))
val maxNotifications =
computeMaxKeyguardNotifications(rows, availableSpace = 0f, shelfHeight = 0f)
assertThat(maxNotifications).isEqualTo(0)
}
@Test
fun computeMaxKeyguardNotifications_infiniteSpace_returnsAll() {
val numberOfRows = 30
val rows = createLockscreenRows(numberOfRows)
val maxNotifications = computeMaxKeyguardNotifications(rows, Float.MAX_VALUE)
assertThat(maxNotifications).isEqualTo(numberOfRows)
}
@Test
fun computeMaxKeyguardNotifications_spaceForOne_returnsOne() {
val rowHeight = ROW_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight
val shelfHeight =
totalSpaceForEachRow / 2 // In this way shelf absence will not leave room for another.
val spaceForOne = totalSpaceForEachRow
val rows =
listOf(
createMockRow(rowHeight, visibleOnLockscreen = true),
createMockRow(rowHeight, visibleOnLockscreen = true))
val maxNotifications =
computeMaxKeyguardNotifications(
rows, availableSpace = spaceForOne, shelfHeight = shelfHeight)
assertThat(maxNotifications).isEqualTo(1)
}
@Test
fun computeMaxKeyguardNotifications_spaceForOne_shelfUsableForLastNotification_returnsTwo() {
val rowHeight = ROW_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight
val shelfHeight = totalSpaceForEachRow + NOTIFICATION_PADDING
val spaceForOne = totalSpaceForEachRow
val rows =
listOf(
createMockRow(rowHeight, visibleOnLockscreen = true),
createMockRow(rowHeight, visibleOnLockscreen = true))
val maxNotifications =
computeMaxKeyguardNotifications(
rows, availableSpace = spaceForOne, shelfHeight = shelfHeight)
assertThat(maxNotifications).isEqualTo(1)
}
@Test
fun computeMaxKeyguardNotifications_invisibleOnLockscreen_returnsZero() {
val rows = listOf(createMockRow(visibleOnLockscreen = false))
val maxNotifications = computeMaxKeyguardNotifications(rows, Float.MAX_VALUE)
assertThat(maxNotifications).isEqualTo(0)
}
@Test
fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
val rowHeight = ROW_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight
val spaceForTwo = totalSpaceForEachRow * 2 + NOTIFICATION_PADDING
val rows =
listOf(
createMockRow(rowHeight, visibleOnLockscreen = true),
createMockRow(rowHeight, visibleOnLockscreen = true),
createMockRow(rowHeight, visibleOnLockscreen = true))
val maxNotifications = computeMaxKeyguardNotifications(rows, spaceForTwo, shelfHeight = 0f)
assertThat(maxNotifications).isEqualTo(2)
}
@Test
fun computeHeight_returnsLessThanAvailableSpaceUsedToCalculateMaxNotifications() {
val rowHeight = ROW_HEIGHT
val shelfHeight = SHELF_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
val availableSpace = totalSpaceForEachRow * 2
val rows =
listOf(
createMockRow(rowHeight, visibleOnLockscreen = true),
createMockRow(rowHeight, visibleOnLockscreen = true),
createMockRow(rowHeight, visibleOnLockscreen = true))
val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
assertThat(maxNotifications).isEqualTo(2)
val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, SHELF_HEIGHT)
assertThat(height).isAtMost(availableSpace + SHELF_HEIGHT)
}
@Test
fun computeHeight_allInvisibleToLockscreen_NotInLockscreen_returnsHigherThanZero() {
setOnLockscreen(false)
val rowHeight = 10f
setupChildren(listOf(createMockRow(rowHeight, visibleOnLockscreen = false)))
val height =
sizeCalculator.computeHeight(
stackLayout, maxNotifications = Int.MAX_VALUE, SHELF_HEIGHT)
assertThat(height).isGreaterThan(rowHeight)
}
@Test
fun computeHeight_allInvisibleToLockscreen_onLockscreen_returnsZero() {
setOnLockscreen(true)
setupChildren(listOf(createMockRow(visibleOnLockscreen = false)))
val height =
sizeCalculator.computeHeight(
stackLayout, maxNotifications = Int.MAX_VALUE, SHELF_HEIGHT)
assertThat(height).isEqualTo(0)
}
private fun computeMaxKeyguardNotifications(
rows: List<ExpandableView>,
availableSpace: Float,
shelfHeight: Float = SHELF_HEIGHT
): Int {
setupChildren(rows)
return sizeCalculator.computeMaxKeyguardNotifications(
stackLayout, availableSpace, shelfHeight)
}
private fun setupChildren(children: List<ExpandableView>) {
whenever(stackLayout.getChildAt(any())).thenAnswer { invocation ->
val inx = invocation.getArgument<Int>(0)
return@thenAnswer children[inx]
}
whenever(stackLayout.childCount).thenReturn(children.size)
}
private fun createLockscreenRows(number: Int): List<ExpandableNotificationRow> =
(1..number).map { createMockRow(visibleOnLockscreen = true) }.toList()
private fun createMockRow(
height: Float = ROW_HEIGHT,
visibleOnLockscreen: Boolean = true,
isRemoved: Boolean = false,
visibility: Int = VISIBLE,
summaryOfSuppressed: Boolean = false
): ExpandableNotificationRow {
val row = mock(ExpandableNotificationRow::class.java)
val entry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
whenever(entry.sbn).thenReturn(sbn)
whenever(row.entry).thenReturn(entry)
whenever(row.isRemoved).thenReturn(isRemoved)
whenever(row.visibility).thenReturn(visibility)
whenever(notificationLockscreenUserManager.shouldShowOnKeyguard(entry))
.thenReturn(visibleOnLockscreen)
whenever(groupManager.isSummaryOfSuppressedGroup(sbn)).thenReturn(summaryOfSuppressed)
whenever(row.getMinHeight(any())).thenReturn(height.toInt())
whenever(row.intrinsicHeight).thenReturn(height.toInt())
return row
}
private fun setOnLockscreen(onLockscreen: Boolean) {
whenever(sysuiStatusBarStateController.state)
.thenReturn(
if (onLockscreen) {
KEYGUARD
} else {
SHADE
})
}
/** Default dimensions for tests that don't overwrite them. */
companion object {
const val GAP_HEIGHT = 12f
const val NOTIFICATION_PADDING = 3f
const val SHELF_HEIGHT = 14f
const val ROW_HEIGHT = SHELF_HEIGHT * 3
}
}

View File

@@ -104,7 +104,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -119,12 +118,12 @@ import com.android.systemui.statusbar.notification.ConversationNotificationManag
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
@@ -174,8 +173,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private NotificationShelfController mNotificationShelfController;
@Mock
private NotificationGroupManagerLegacy mGroupManager;
@Mock
private KeyguardStatusBarView mKeyguardStatusBar;
@Mock
private KeyguardUserSwitcherView mUserSwitcherView;
@@ -200,10 +197,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private DynamicPrivacyController mDynamicPrivacyController;
@Mock
private ShadeController mShadeController;
@Mock
private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock
private NotificationEntryManager mNotificationEntryManager;
@Mock
private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
@@ -336,6 +329,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private SysUiState mSysUiState;
@Mock
private NotificationListContainer mNotificationListContainer;
@Mock
private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -466,7 +461,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mFeatureFlags,
coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
mFalsingManager, new FalsingCollectorFake(),
mNotificationLockscreenUserManager, mNotificationEntryManager,
mNotificationEntryManager,
mKeyguardStateController,
mStatusBarStateController,
mStatusBarWindowStateController,
@@ -484,7 +479,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mKeyguardUserSwitcherComponentFactory,
mKeyguardStatusBarViewComponentFactory,
mLockscreenShadeTransitionController,
mGroupManager,
mNotificationAreaController,
mAuthController,
mScrimController,
@@ -516,7 +510,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mSysUiState,
mKeyguardUnlockAnimationController,
mNotificationListContainer,
mPanelEventsEmitter);
mPanelEventsEmitter,
mNotificationStackSizeCalculator);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},