Replace NSSLController usage in NotifShelf w/ NSSL

Under a unidirectional architecture, Views should not have access to Controllers.

In the future, we may want to explore decoupling NotificationShelf from NotificationStackScrollLayout entirely, and wire up their interaction externally via ViewBinders / Controllers. That is out of scope of this CL.

Note that we had to pull in NotificationRoundnessManager as a dependency in NotificationShelf. This is needed for functionality that was originally exposed via the NSSLController. NRM is effectively a View class, serving as a Data Store for the NSSL hierarchy, so it's OK w/r/t unidirectionality, especially considering NotificationShelf has a reference to the NSSL anyway. In the future, when we move some of the shelf logic back into the NSSL / StackScrollAlgorithm (specifically, the part that traverses the children of the NSSL), then we can drop the dependency on the RoundnessManager.

Bug: 271161129
Test: atest SystemUITests
Change-Id: I5887c0b0c62d2464f8818fdf42f8e38484f3a59a
This commit is contained in:
Steve Elliott
2023-04-04 21:29:08 -04:00
parent 540f52499b
commit 6e20c37aeb
7 changed files with 186 additions and 18 deletions

View File

@@ -3333,7 +3333,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mGestureRecorder = recorder;
mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
mNotificationStackScrollLayoutController.setShelfController(
notificationShelfController);
}
mNotificationShelfController = notificationShelfController;
mLockscreenShadeTransitionController.bindController(notificationShelfController);
updateMaxDisplayedNotifications(true);

View File

@@ -53,6 +53,8 @@ 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.ExpandableViewState;
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.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -100,6 +102,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
private boolean mShelfRefactorFlagEnabled;
private boolean mCanModifyColorOfNotifications;
private boolean mCanInteract;
private NotificationStackScrollLayout mHostLayout;
private NotificationRoundnessManager mRoundnessManager;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -136,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController hostLayoutController) {
assertRefactorFlagDisabled();
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -143,6 +148,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
});
}
public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
NotificationRoundnessManager roundnessManager) {
if (!checkRefactorFlagEnabled()) return;
mAmbientState = ambientState;
mHostLayout = hostLayout;
mRoundnessManager = roundnessManager;
}
private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
@@ -234,7 +247,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
}
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -277,6 +290,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
private int getSpeedBumpIndex() {
if (mShelfRefactorFlagEnabled) {
return mHostLayout.getSpeedBumpIndex();
} else {
return mHostLayoutController.getSpeedBumpIndex();
}
}
/**
* @param fractionToShade Fraction of lockscreen to shade transition
* @param shortestWidth Shortest width to use for lockscreen shelf
@@ -389,8 +410,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
int baseZHeight = mAmbientState.getBaseZHeight();
int clipTopAmount = 0;
for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
ExpandableView child = mHostLayoutController.getChildAt(i);
for (int i = 0; i < getHostLayoutChildCount(); i++) {
ExpandableView child = getHostLayoutChildAt(i);
if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
continue;
}
@@ -475,11 +496,11 @@ public class NotificationShelf extends ActivatableNotificationView implements St
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
View child = mHostLayoutController.getChildAt(i);
for (int i = 0; i < getHostLayoutChildCount(); i++) {
View child = getHostLayoutChildAt(i);
if (!(child instanceof ExpandableNotificationRow)
|| child.getVisibility() == GONE) {
continue;
@@ -494,6 +515,22 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
private ExpandableView getHostLayoutChildAt(int index) {
if (mShelfRefactorFlagEnabled) {
return (ExpandableView) mHostLayout.getChildAt(index);
} else {
return mHostLayoutController.getChildAt(index);
}
}
private int getHostLayoutChildCount() {
if (mShelfRefactorFlagEnabled) {
return mHostLayout.getChildCount();
} else {
return mHostLayoutController.getChildCount();
}
}
private boolean canModifyColorOfNotifications() {
if (mShelfRefactorFlagEnabled) {
return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
@@ -516,7 +553,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
&& anv == mAmbientState.getTrackedHeadsUpRow();
final boolean shouldUpdateCornerRoundness = viewStart < shelfStart
&& !mHostLayoutController.isViewAffectedBySwipe(anv)
&& !isViewAffectedBySwipe(anv)
&& !isUnlockedHeadsUp
&& !isHunGoingToShade
&& !anv.isAboveShelf()
@@ -568,6 +605,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
}
private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
if (!mShelfRefactorFlagEnabled) {
return mHostLayoutController.isViewAffectedBySwipe(expandableView);
} else {
return mRoundnessManager.isViewAffectedBySwipe(expandableView);
}
}
/**
* Clips transient views to the top of the shelf - Transient views are only used for
* disappearing views/animations and need to be clipped correctly by the shelf to ensure they
@@ -575,8 +620,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
* swipes quickly.
*/
private void clipTransientViews() {
for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
View transientView = mHostLayoutController.getTransientView(i);
for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
View transientView = getHostLayoutTransientView(i);
if (transientView instanceof ExpandableView) {
ExpandableView transientExpandableView = (ExpandableView) transientView;
updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -584,6 +629,22 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
private View getHostLayoutTransientView(int index) {
if (mShelfRefactorFlagEnabled) {
return mHostLayout.getTransientView(index);
} else {
return mHostLayoutController.getTransientView(index);
}
}
private int getHostLayoutTransientViewCount() {
if (mShelfRefactorFlagEnabled) {
return mHostLayout.getTransientViewCount();
} else {
return mHostLayoutController.getTransientViewCount();
}
}
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
if (getClipTopAmount() != 0) {
@@ -982,8 +1043,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
private void assertRefactorFlagDisabled() {
if (mShelfRefactorFlagEnabled) {
throw new IllegalStateException(
"Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
NotificationShelfController.throwIllegalFlagStateError(false);
}
}
@@ -1012,7 +1072,15 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf);
}
private int getIndexOfViewInHostLayout(ExpandableView child) {
if (mShelfRefactorFlagEnabled) {
return mHostLayout.indexOfChild(child);
} else {
return mHostLayoutController.indexOfChild(child);
}
}
/**
@@ -1027,6 +1095,11 @@ public class NotificationShelf extends ActivatableNotificationView implements St
mShelfRefactorFlagEnabled = enabled;
}
public void requestRoundnessResetFor(ExpandableView child) {
if (!checkRefactorFlagEnabled()) return;
child.requestRoundnessReset(SHELF_SCROLL);
}
/**
* This method resets the OnScroll roundness of a view to 0f
* <p>

View File

@@ -16,8 +16,11 @@
package com.android.systemui.statusbar
import android.util.Log
import android.view.View
import android.view.View.OnClickListener
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -51,4 +54,29 @@ interface NotificationShelfController {
/** @see View.setOnClickListener */
fun setOnClickListener(listener: OnClickListener)
companion object {
@JvmStatic
fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
throwIllegalFlagStateError(expected = false)
}
}
@JvmStatic
fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
if (!enabled) {
Log.wtf("NotifShelf", getErrorMessage(expected = true))
}
}
@JvmStatic
fun throwIllegalFlagStateError(expected: Boolean): Nothing =
error(getErrorMessage(expected))
private fun getErrorMessage(expected: Boolean): String =
"Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
if (expected) "disabled" else "enabled"
}
}

View File

@@ -38,6 +38,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.NotificationTapHelper
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.util.kotlin.getValue
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,10 +61,13 @@ constructor(
private val a11yManager: AccessibilityManager,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
) : NotificationShelfController {
private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
override val view: NotificationShelf
get() = shelf
get() = unsupported
init {
shelf.apply {
@@ -84,6 +89,10 @@ constructor(
falsingCollector,
)
.init()
hostController.setShelf(shelf)
hostController.setOnNotificationRemovedListener { child, _ ->
view.requestRoundnessResetFor(child)
}
}
override val intrinsicHeight: Int
@@ -101,16 +110,14 @@ constructor(
override fun bind(
ambientState: AmbientState,
notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) {
shelf.bind(ambientState, notificationStackScrollLayoutController)
}
) = unsupported
override fun setOnClickListener(listener: View.OnClickListener) {
shelf.setOnClickListener(listener)
}
private val unsupported: Nothing
get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
}
/** Binds a [NotificationShelf] to its backend. */

View File

@@ -5137,8 +5137,26 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
requestChildrenUpdate();
}
public void setShelf(NotificationShelf shelf) {
if (!NotificationShelfController.checkRefactorFlagEnabled(
mAmbientState.getFeatureFlags())) {
return;
}
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
removeView(mShelf);
}
mShelf = shelf;
addView(mShelf, index);
mAmbientState.setShelf(mShelf);
mStateAnimator.setShelf(mShelf);
shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setShelfController(NotificationShelfController notificationShelfController) {
NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);

View File

@@ -73,6 +73,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
@@ -1382,6 +1383,7 @@ public class NotificationStackScrollLayoutController {
}
public void setShelfController(NotificationShelfController notificationShelfController) {
NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
mView.setShelfController(notificationShelfController);
}
@@ -1593,6 +1595,11 @@ public class NotificationStackScrollLayoutController {
mView.setOnNotificationRemovedListener(listener);
}
public void setShelf(NotificationShelf shelf) {
if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
mView.setShelf(shelf);
}
/**
* Enum for UiEvent logged from this class
*/

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 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.util.kotlin
import dagger.Lazy
import kotlin.reflect.KProperty
/**
* Extension operator that allows developers to use [dagger.Lazy] as a property delegate:
* ```kotlin
* class MyClass @Inject constructor(
* lazyDependency: dagger.Lazy<Foo>,
* ) {
* val dependency: Foo by lazyDependency
* }
* ```
*/
operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()