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

Commit 38ebf0e3 authored by Steve Elliott's avatar Steve Elliott
Browse files

Extract maximum amount + speedbump index from NIC

Flag: ACONFIG com.android.systemui.notifications_icon_container_refactor DEVELOPMENT
Fixes: 309555139
Bug: 278765923
Test: manual
  1. Verify AOD icons and overflow dot render correctly.
    a. On AOD
    b. On bypass lock screen
  2. Verify Status Bar icons and overflow dot render correctly.
  3. Verify notification shelf icons and overflow dot render correctly.
    a. In expanded shade
    b. On lock screen
Change-Id: I5a5b335708461a3921e2209c1886e0ce4d79e74b
parent 0234597b
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -603,7 +603,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
                    mAodIconsBindHandle.dispose();
                }
                if (nic != null) {
                    nic.setOnLockScreen(true);
                    final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind(
                            nic,
                            mAodIconsViewModel,
+0 −1
Original line number Diff line number Diff line
@@ -86,7 +86,6 @@ constructor(
        }

        if (NotificationIconContainerRefactor.isEnabled) {
            nic.setOnLockScreen(true)
            nicBindingDisposable?.dispose()
            nicBindingDisposable =
                NotificationIconContainerViewBinder.bind(
+30 −3
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ public class NotificationShelf extends ActivatableNotificationView {
    private int mIndexOfFirstViewInShelf = -1;
    private float mCornerAnimationDistance;
    private float mActualWidth = -1;
    private int mMaxIconsOnLockscreen;
    private final RefactorFlag mSensitiveRevealAnim =
            RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
    private boolean mCanModifyColorOfNotifications;
@@ -136,6 +137,7 @@ public class NotificationShelf extends ActivatableNotificationView {
        Resources res = getResources();
        mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
        mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
        mMaxIconsOnLockscreen = res.getInteger(R.integer.max_notif_icons_on_lockscreen);

        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
@@ -271,6 +273,7 @@ public class NotificationShelf extends ActivatableNotificationView {
    }

    private int getSpeedBumpIndex() {
        NotificationIconContainerRefactor.assertInLegacyMode();
        return mHostLayout.getSpeedBumpIndex();
    }

@@ -280,6 +283,7 @@ public class NotificationShelf extends ActivatableNotificationView {
     */
    @VisibleForTesting
    public void updateActualWidth(float fractionToShade, float shortestWidth) {
        NotificationIconContainerRefactor.assertInLegacyMode();
        final float actualWidth = mAmbientState.isOnKeyguard()
                ? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade)
                : getWidth();
@@ -290,6 +294,15 @@ public class NotificationShelf extends ActivatableNotificationView {
        mActualWidth = actualWidth;
    }

    private void setActualWidth(float actualWidth) {
        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
        setBackgroundWidth((int) actualWidth);
        if (mShelfIcons != null) {
            mShelfIcons.setActualLayoutWidth((int) actualWidth);
        }
        mActualWidth = actualWidth;
    }

    @Override
    public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
        super.getBoundsOnScreen(outRect, clipToParent);
@@ -467,12 +480,26 @@ public class NotificationShelf extends ActivatableNotificationView {

        final float fractionToShade = Interpolators.STANDARD.getInterpolation(
                mAmbientState.getFractionToShade());

        if (NotificationIconContainerRefactor.isEnabled()) {
            if (mAmbientState.isOnKeyguard()) {
                float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1);
                float shortestWidth = mShelfIcons.calculateWidthFor(numViews);
                float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade);
                setActualWidth(actualWidth);
            } else {
                setActualWidth(getWidth());
            }
        } else {
            final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf);
            updateActualWidth(fractionToShade, shortestWidth);
        }

        // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
        setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
        if (!NotificationIconContainerRefactor.isEnabled()) {
            mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
        }
        mShelfIcons.calculateIconXTranslations();
        mShelfIcons.applyIconStates();
        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+95 −56
Original line number Diff line number Diff line
@@ -25,9 +25,7 @@ import androidx.lifecycle.lifecycleScope
import com.android.internal.policy.SystemBarUtils
import com.android.internal.statusbar.StatusBarIcon
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -39,21 +37,15 @@ import com.android.systemui.statusbar.notification.icon.ui.viewmodel.Notificatio
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
import com.android.systemui.util.asIndenting
import com.android.systemui.util.kotlin.mapValuesNotNullTo
import com.android.systemui.util.kotlin.stateFlow
import com.android.systemui.util.printCollection
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import dagger.Binds
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -134,6 +126,7 @@ object NotificationIconContainerViewBinder {
    ): DisposableHandle {
        return view.repeatWhenAttached {
            lifecycleScope.launch {
                view.setUseIncreasedIconScale(true)
                launch {
                    viewModel.icons.bindIcons(
                        view,
@@ -229,7 +222,6 @@ object NotificationIconContainerViewBinder {
                ->
                FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
            }

        val failedBindings = mutableSetOf<String>()
        val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
        var prevIcons = NotificationIconsViewData()
@@ -237,12 +229,13 @@ object NotificationIconContainerViewBinder {
            val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
            prevIcons = iconsData

            // Lookup 1:1 group icon replacements
            val replacingIcons: ArrayMap<String, StatusBarIcon> =
                iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, info) ->
                    boundViewsByNotifKey[info.notifKey]?.first?.statusBarIcon
                iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, notifKey) ->
                    boundViewsByNotifKey[notifKey]?.first?.statusBarIcon
                }
            view.setReplacingIcons(replacingIcons)

            view.withIconReplacements(replacingIcons) {
                // Remove and unbind.
                for (notifKey in iconsDiff.removed) {
                    failedBindings.remove(notifKey)
                    val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue
@@ -250,15 +243,17 @@ object NotificationIconContainerViewBinder {
                    job.cancel()
                }

            val toAdd: Sequence<String> =
                iconsDiff.added.asSequence().map { it.notifKey } + failedBindings
                // Add and bind.
                val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings
                for ((idx, notifKey) in toAdd.withIndex()) {
                    // Lookup the StatusBarIconView from the store.
                    val sbiv = viewStore.iconView(notifKey)
                    if (sbiv == null) {
                        failedBindings.add(notifKey)
                        continue
                    }
                // The view might still be transiently added if it was just removed and added again
                    // The view might still be transiently added if it was just removed and added
                    // again
                    view.removeTransientView(sbiv)
                    view.addView(sbiv, idx)
                    boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
@@ -272,13 +267,31 @@ object NotificationIconContainerViewBinder {
                        )
                }

            notifyBindingFailures(failedBindings)
                // Set the maximum number of icons to show in the container. Any icons over this
                // amount will render as an "overflow dot".
                val maxIconsAmount: Int =
                    when (iconsData.limitType) {
                        LimitType.MaximumIndex -> {
                            iconsData.visibleIcons
                                .asSequence()
                                .take(iconsData.iconLimit)
                                .count { info -> info.notifKey in boundViewsByNotifKey }
                        }
                        LimitType.MaximumAmount -> {
                            iconsData.iconLimit
                        }
                    }
                view.setMaxIconsAmount(maxIconsAmount)

            view.setChangingViewPositions(true)
                // Track the binding failures so that they appear in dumpsys.
                notifyBindingFailures(failedBindings)

                // Re-sort notification icons
            val expectedChildren =
                iconsData.visibleKeys.mapNotNull { boundViewsByNotifKey[it.notifKey]?.first }
                view.changeViewPositions {
                    val expectedChildren: List<StatusBarIconView> =
                        iconsData.visibleIcons.mapNotNull {
                            boundViewsByNotifKey[it.notifKey]?.first
                        }
                    val childCount = view.childCount
                    for (i in 0 until childCount) {
                        val actual = view.getChildAt(i)
@@ -289,10 +302,36 @@ object NotificationIconContainerViewBinder {
                        view.removeView(expected)
                        view.addView(expected, i)
                    }
            view.setChangingViewPositions(false)
                }
            }
            // Recalculate all icon positions, to reflect our updates.
            view.calculateIconXTranslations()
        }
    }

            view.setReplacingIcons(null)
    /**
     * Track which groups are being replaced with a different icon instance, but with the same
     * visual icon. This prevents a weird animation where it looks like an icon disappears and
     * reappears unchanged.
     */
    // TODO(b/305739416): Ideally we wouldn't swap out the StatusBarIconView at all, and instead use
    //  a single SBIV instance for the group. Then this whole concept can go away.
    private inline fun <R> NotificationIconContainer.withIconReplacements(
        replacements: ArrayMap<String, StatusBarIcon>,
        block: () -> R
    ): R {
        setReplacingIcons(replacements)
        return block().also { setReplacingIcons(null) }
    }

    /**
     * Any invocations of [NotificationIconContainer.addView] /
     * [NotificationIconContainer.removeView] inside of [block] will not cause a new add / remove
     * animation.
     */
    private inline fun <R> NotificationIconContainer.changeViewPositions(block: () -> R): R {
        setChangingViewPositions(true)
        return block().also { setChangingViewPositions(false) }
    }

    /** External storage for [StatusBarIconView] instances. */
+8 −1
Original line number Diff line number Diff line
@@ -15,10 +15,13 @@
 */
package com.android.systemui.statusbar.notification.icon.ui.viewmodel

import android.content.res.Resources
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
import javax.inject.Inject
@@ -35,9 +38,12 @@ constructor(
    iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
    keyguardInteractor: KeyguardInteractor,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    @Main resources: Resources,
    shadeInteractor: ShadeInteractor,
) {

    private val maxIcons = resources.getInteger(R.integer.max_notif_icons_on_aod)

    /** Are changes to the icon container animated? */
    val areContainerChangesAnimated: Flow<Boolean> =
        combine(
@@ -67,7 +73,8 @@ constructor(
    val icons: Flow<NotificationIconsViewData> =
        iconsInteractor.aodNotifs.map { entries ->
            NotificationIconsViewData(
                visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
                visibleIcons = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
                iconLimit = maxIcons,
            )
        }
}
Loading