ViewModel+Binder for NotificationStackScrollLayout

This change lays a foundation for more notifications code to be migrated
to the Recommended Architecture. Right now, it primarily serves as a
means to integrate the NotificationShelfViewBinder into the codebase
without needed the NotificationShelfController interface any longer. As
more and more child Views of the NSSL are migrated, the NSSLViewBinder
will continue to grow.

If we ever decide to migrate the NSSL itself, then code from the
NSSLController will be moved into the NSSLViewBinder under a process
similar to that implemented for the NotificationShelf.

Bug: 271161129
Test: atest SystemUITests
Change-Id: I8f605f8d7b4722d3c2e065c7b37cc4f23e6cc99a
This commit is contained in:
Steve Elliott
2023-04-12 13:24:23 -04:00
parent 0ffb527e5f
commit 84989e0a10
7 changed files with 159 additions and 52 deletions

View File

@@ -33,12 +33,9 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
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.awaitCancellation
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
* Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
@@ -47,39 +44,12 @@ import kotlinx.coroutines.flow.onEach
* removed, this class can go away and the ViewBinder can be used directly.
*/
@CentralSurfacesScope
class NotificationShelfViewBinderWrapperControllerImpl
@Inject
constructor(
private val shelf: NotificationShelf,
private val viewModel: NotificationShelfViewModel,
featureFlags: FeatureFlags,
private val falsingManager: FalsingManager,
hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
private val notificationIconAreaController: NotificationIconAreaController,
) : NotificationShelfController {
private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor() :
NotificationShelfController {
override val view: NotificationShelf
get() = unsupported
init {
shelf.apply {
setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
}
}
fun init() {
NotificationShelfViewBinder.bind(viewModel, shelf, falsingManager)
hostController.setShelf(shelf)
hostController.setOnNotificationRemovedListener { child, _ ->
view.requestRoundnessResetFor(child)
}
notificationIconAreaController.setShelfIcons(shelf.shelfIcons)
}
override val intrinsicHeight: Int
get() = unsupported
@@ -99,21 +69,32 @@ constructor(
get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
}
/** Binds a [NotificationShelf] to its backend. */
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
object NotificationShelfViewBinder {
fun bind(
viewModel: NotificationShelfViewModel,
shelf: NotificationShelf,
viewModel: NotificationShelfViewModel,
falsingManager: FalsingManager,
featureFlags: FeatureFlags,
notificationIconAreaController: NotificationIconAreaController,
) {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.canModifyColorOfNotifications
.onEach(shelf::setCanModifyColorOfNotifications)
.launchIn(this)
viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
registerViewListenersWhileAttached(shelf, viewModel)
shelf.apply {
setRefactorFlagEnabled(true)
useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
// TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
notificationIconAreaController.setShelfIcons(shelfIcons)
repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.canModifyColorOfNotifications.collect(
::setCanModifyColorOfNotifications
)
}
launch { viewModel.isClickable.collect(::setCanInteract) }
registerViewListenersWhileAttached(shelf, viewModel)
}
}
}
}

View File

@@ -2839,6 +2839,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* @param listener callback for notification removed
*/
public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
mOnNotificationRemovedListener = listener;
}
@@ -2852,10 +2853,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!mChildTransferInProgress) {
onViewRemovedInternal(expandableView, this);
}
if (mOnNotificationRemovedListener != null) {
mOnNotificationRemovedListener.onNotificationRemoved(
expandableView,
mChildTransferInProgress);
if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
mShelf.requestRoundnessResetFor(expandableView);
} else {
if (mOnNotificationRemovedListener != null) {
mOnNotificationRemovedListener.onNotificationRemoved(
expandableView,
mChildTransferInProgress);
}
}
}

View File

@@ -105,11 +105,14 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -122,16 +125,17 @@ import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
import com.android.systemui.util.settings.SecureSettings;
import kotlin.Unit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Unit;
/**
* Controller for {@link NotificationStackScrollLayout}.
*/
@@ -151,6 +155,8 @@ public class NotificationStackScrollLayoutController {
private final ConfigurationController mConfigurationController;
private final ZenModeController mZenModeController;
private final MetricsLogger mMetricsLogger;
private final Optional<NotificationListViewModel> mViewModel;
private final DumpManager mDumpManager;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
@@ -175,6 +181,8 @@ public class NotificationStackScrollLayoutController {
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
private final NotificationStackScrollLogger mLogger;
private final NotificationIconAreaController mNotifIconAreaController;
private final GroupExpansionManager mGroupExpansionManager;
private final NotifPipelineFlags mNotifPipelineFlags;
private final SeenNotificationsProvider mSeenNotificationsProvider;
@@ -642,6 +650,7 @@ public class NotificationStackScrollLayoutController {
KeyguardBypassController keyguardBypassController,
ZenModeController zenModeController,
NotificationLockscreenUserManager lockscreenUserManager,
Optional<NotificationListViewModel> nsslViewModel,
MetricsLogger metricsLogger,
DumpManager dumpManager,
FalsingCollector falsingCollector,
@@ -665,6 +674,7 @@ public class NotificationStackScrollLayoutController {
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
NotificationIconAreaController notifIconAreaController,
FeatureFlags featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
@@ -686,6 +696,7 @@ public class NotificationStackScrollLayoutController {
mKeyguardBypassController = keyguardBypassController;
mZenModeController = zenModeController;
mLockscreenUserManager = lockscreenUserManager;
mViewModel = nsslViewModel;
mMetricsLogger = metricsLogger;
mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -707,6 +718,7 @@ public class NotificationStackScrollLayoutController {
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mSeenNotificationsProvider = seenNotificationsProvider;
mShadeController = shadeController;
mNotifIconAreaController = notifIconAreaController;
mFeatureFlags = featureFlags;
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -820,6 +832,10 @@ public class NotificationStackScrollLayoutController {
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
mViewModel.ifPresent(
vm -> NotificationListViewBinder
.bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
}
private boolean isInVisibleLocation(NotificationEntry entry) {

View File

@@ -0,0 +1,51 @@
/*
* 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.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
@JvmStatic
fun bind(
view: NotificationStackScrollLayout,
viewModel: NotificationListViewModel,
falsingManager: FalsingManager,
featureFlags: FeatureFlags,
iconAreaController: NotificationIconAreaController,
) {
val shelf =
LayoutInflater.from(view.context)
.inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
NotificationShelfViewBinder.bind(
shelf,
viewModel.shelf,
falsingManager,
featureFlags,
iconAreaController
)
view.setShelf(shelf)
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
/** ViewModel for the list of notifications. */
class NotificationListViewModel(
val shelf: NotificationShelfViewModel,
)
@Module
object NotificationListViewModelModule {
@JvmStatic
@Provides
fun maybeProvideViewModel(
featureFlags: FeatureFlags,
shelfViewModel: Provider<NotificationShelfViewModel>,
): Optional<NotificationListViewModel> =
if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
Optional.of(NotificationListViewModel(shelfViewModel.get()))
} else {
Optional.empty()
}
}

View File

@@ -55,6 +55,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModelModule;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
@@ -87,7 +88,10 @@ import javax.inject.Named;
import javax.inject.Provider;
@Module(subcomponents = StatusBarFragmentComponent.class,
includes = { ActivatableNotificationViewModelModule.class })
includes = {
ActivatableNotificationViewModelModule.class,
NotificationListViewModelModule.class,
})
public abstract class StatusBarViewModule {
public static final String SHADE_HEADER = "large_screen_shade_header";
@@ -117,9 +121,7 @@ public abstract class StatusBarViewModule {
NotificationShelfComponent.Builder notificationShelfComponentBuilder,
NotificationShelf notificationShelf) {
if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get();
impl.init();
return impl;
return newImpl.get();
} else {
NotificationShelfComponent component = notificationShelfComponentBuilder
.notificationShelf(notificationShelf)

View File

@@ -72,9 +72,11 @@ import com.android.systemui.statusbar.notification.collection.render.SectionHead
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -92,6 +94,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
/**
* Tests for {@link NotificationStackScrollLayoutController}.
*/
@@ -138,6 +142,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Mock private SecureSettings mSecureSettings;
@Mock private NotificationIconAreaController mIconAreaController;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -430,6 +435,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mKeyguardBypassController,
mZenModeController,
mNotificationLockscreenUserManager,
Optional.<NotificationListViewModel>empty(),
mMetricsLogger,
mDumpManager,
new FalsingCollectorFake(),
@@ -453,6 +459,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
mIconAreaController,
mFeatureFlags,
mNotificationTargetsHelper,
mSecureSettings,