From 84989e0a10361cfc9b05a99c56ec5b9ff65bf15f Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 12 Apr 2023 13:24:23 -0400 Subject: [PATCH] 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 --- .../viewbinder/NotificationShelfViewBinder.kt | 65 +++++++------------ .../stack/NotificationStackScrollLayout.java | 13 ++-- ...tificationStackScrollLayoutController.java | 20 +++++- .../viewbinder/NotificationListViewBinder.kt | 51 +++++++++++++++ .../ui/viewmodel/NotificationListViewModel.kt | 45 +++++++++++++ .../phone/dagger/StatusBarViewModule.java | 10 +-- ...cationStackScrollLayoutControllerTest.java | 7 ++ 7 files changed, 159 insertions(+), 52 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index c82318913ced0..99c6ddb8cfaa2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -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, - 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) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 555d5027c7b64..587783d87ba5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -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); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9979cc4fa9d5a..b69ce38613422 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -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 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 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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt new file mode 100644 index 0000000000000..45ae4e0afc3ac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt new file mode 100644 index 0000000000000..aab1c2b877e8b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -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, + ): Optional = + if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + Optional.of(NotificationListViewModel(shelfViewModel.get())) + } else { + Optional.empty() + } +} 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 95d36b082b57b..ef86162aecf53 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 @@ -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) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 0ae012cd5e908..fbbb921f96a0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -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 mStateListenerArgumentCaptor; @@ -430,6 +435,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mKeyguardBypassController, mZenModeController, mNotificationLockscreenUserManager, + Optional.empty(), mMetricsLogger, mDumpManager, new FalsingCollectorFake(), @@ -453,6 +459,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mStackLogger, mLogger, mNotificationStackSizeCalculator, + mIconAreaController, mFeatureFlags, mNotificationTargetsHelper, mSecureSettings,