Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84989e0a authored by Steve Elliott's avatar Steve Elliott
Browse files

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
parent 0ffb527e
Loading
Loading
Loading
Loading
+23 −42
Original line number Diff line number Diff line
@@ -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,24 +69,35 @@ 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 {
        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) {
                viewModel.canModifyColorOfNotifications
                    .onEach(shelf::setCanModifyColorOfNotifications)
                    .launchIn(this)
                viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
                    launch {
                        viewModel.canModifyColorOfNotifications.collect(
                            ::setCanModifyColorOfNotifications
                        )
                    }
                    launch { viewModel.isClickable.collect(::setCanInteract) }
                    registerViewListenersWhileAttached(shelf, viewModel)
                }
            }
        }
    }

    private suspend fun registerViewListenersWhileAttached(
        shelf: NotificationShelf,
+9 −4
Original line number Diff line number Diff line
@@ -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,12 +2853,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
        if (!mChildTransferInProgress) {
            onViewRemovedInternal(expandableView, this);
        }
        if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
            mShelf.requestRoundnessResetFor(expandableView);
        } else {
            if (mOnNotificationRemovedListener != null) {
                mOnNotificationRemovedListener.onNotificationRemoved(
                        expandableView,
                        mChildTransferInProgress);
            }
        }
    }

    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
    public void cleanUpViewStateForEntry(NotificationEntry entry) {
+18 −2
Original line number Diff line number Diff line
@@ -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) {
+51 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
+45 −0
Original line number Diff line number Diff line
/*
 * 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()
        }
}
Loading