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

Commit e38d3871 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[View Controllers] (3/3) Move (most of) KSBVC's alpha calculation out...

Merge "[View Controllers] (3/3) Move (most of) KSBVC's alpha calculation out of NPVController and into KSBVC." into sc-v2-dev
parents cd0a7abf be11a89c
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.keyguard.dagger;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;

import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -36,8 +37,8 @@ public interface KeyguardStatusBarViewComponent {
    interface Factory {
        KeyguardStatusBarViewComponent build(
                @BindsInstance KeyguardStatusBarView view,
                @BindsInstance KeyguardStatusBarViewController.ViewStateProvider
                        viewStateProvider);
                @BindsInstance NotificationPanelViewController.NotificationPanelViewStateProvider
                        notificationPanelViewStateProvider);
    }

    /** Builds a {@link KeyguardStatusViewController}. */
+84 −21
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.util.MathUtils;
import android.view.View;

import androidx.annotation.NonNull;
@@ -40,6 +41,9 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -57,6 +61,21 @@ import javax.inject.Inject;

/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
    private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

    private float mKeyguardHeadsUpShowingAmount = 0.0f;
    private final AnimatableProperty mHeadsUpShowingAmountAnimation = AnimatableProperty.from(
            "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
            (view, aFloat) -> {
                mKeyguardHeadsUpShowingAmount = aFloat;
                updateViewState();
            },
            view -> mKeyguardHeadsUpShowingAmount,
            R.id.keyguard_hun_animator_tag,
            R.id.keyguard_hun_animator_end_tag,
            R.id.keyguard_hun_animator_start_tag);

    private final CarrierTextController mCarrierTextController;
    private final ConfigurationController mConfigurationController;
    private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -65,7 +84,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
    private final StatusBarIconController mStatusBarIconController;
    private final StatusBarIconController.TintedIconManager.Factory mTintedIconManagerFactory;
    private final BatteryMeterViewController mBatteryMeterViewController;
    private final ViewStateProvider mViewStateProvider;
    private final NotificationPanelViewController.NotificationPanelViewStateProvider
            mNotificationPanelViewStateProvider;
    private final KeyguardStateController mKeyguardStateController;
    private final KeyguardBypassController mKeyguardBypassController;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -176,6 +196,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
            };

    private final List<String> mBlockedIcons;
    private final int mNotificationsHeaderCollideDistance;

    private boolean mBatteryListening;
    private StatusBarIconController.TintedIconManager mTintedIconManager;
@@ -193,6 +214,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
    private boolean mDelayShowingKeyguardStatusBar;
    private int mStatusBarState;
    private boolean mDozing;
    private boolean mShowingKeyguardHeadsUp;

    @Inject
    public KeyguardStatusBarViewController(
@@ -205,7 +227,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
            StatusBarIconController statusBarIconController,
            StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory,
            BatteryMeterViewController batteryMeterViewController,
            ViewStateProvider viewStateProvider,
            NotificationPanelViewController.NotificationPanelViewStateProvider
                    notificationPanelViewStateProvider,
            KeyguardStateController keyguardStateController,
            KeyguardBypassController bypassController,
            KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -220,7 +243,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
        mStatusBarIconController = statusBarIconController;
        mTintedIconManagerFactory = tintedIconManagerFactory;
        mBatteryMeterViewController = batteryMeterViewController;
        mViewStateProvider = viewStateProvider;
        mNotificationPanelViewStateProvider = notificationPanelViewStateProvider;
        mKeyguardStateController = keyguardStateController;
        mKeyguardBypassController = bypassController;
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -245,6 +268,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
                r.getString(com.android.internal.R.string.status_bar_volume),
                r.getString(com.android.internal.R.string.status_bar_alarm_clock),
                r.getString(com.android.internal.R.string.status_bar_call_strength)));
        mNotificationsHeaderCollideDistance = r.getDimensionPixelSize(
                R.dimen.header_notifications_collide_distance);
    }

    @Override
@@ -355,16 +380,20 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
    }

    /**
     * Updates the {@link KeyguardStatusBarView} state based on what the {@link ViewStateProvider}
     * and other controllers provide.
     * Updates the {@link KeyguardStatusBarView} state based on what the
     * {@link NotificationPanelViewController.NotificationPanelViewStateProvider} and other
     * controllers provide.
     */
    public void updateViewState() {
        ViewState newViewState = mViewStateProvider.provideViewState();
        if (!isKeyguardShowing()) {
            return;
        }

        float newAlpha = newViewState.mAlpha * mKeyguardStatusBarAnimateAlpha;
        float alphaQsExpansion = 1 - Math.min(
                1, mNotificationPanelViewStateProvider.getQsExpansionFraction() * 2);
        float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
                * mKeyguardStatusBarAnimateAlpha
                * (1.0f - mKeyguardHeadsUpShowingAmount);

        boolean hideForBypass =
                mFirstBypassAttempt && mKeyguardUpdateMonitor.shouldListenForFace()
@@ -383,6 +412,54 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
        mView.setVisibility(visibility);
    }

    /**
     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
     * during swiping up.
     */
    private float getKeyguardContentsAlpha() {
        float alpha;
        if (isKeyguardShowing()) {
            // When on Keyguard, we hide the header as soon as we expanded close enough to the
            // header
            alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight()
                    / (mView.getHeight() + mNotificationsHeaderCollideDistance);
        } else {
            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
            // soon as we start translating the stack.
            alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight()
                    / mView.getHeight();
        }
        alpha = MathUtils.saturate(alpha);
        alpha = (float) Math.pow(alpha, 0.75);
        return alpha;
    }

    /**
     * Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and
     * whether heads up is visible.
     */
    public void updateForHeadsUp() {
        updateForHeadsUp(true);
    }

    void updateForHeadsUp(boolean animate) {
        boolean showingKeyguardHeadsUp =
                isKeyguardShowing() && mNotificationPanelViewStateProvider.shouldHeadsUpBeVisible();
        if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
            mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
            if (isKeyguardShowing()) {
                PropertyAnimator.setProperty(
                        mView,
                        mHeadsUpShowingAmountAnimation,
                        showingKeyguardHeadsUp ? 1.0f : 0.0f,
                        KEYGUARD_HUN_PROPERTIES,
                        animate);
            } else {
                PropertyAnimator.applyImmediately(mView, mHeadsUpShowingAmountAnimation, 0.0f);
            }
        }
    }

    private boolean isKeyguardShowing() {
        return mStatusBarState == KEYGUARD;
    }
@@ -394,18 +471,4 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
        mView.dump(fd, pw, args);
    }

    /** An interface that provides the desired state of {@link KeyguardStatusBarView}. */
    public interface ViewStateProvider {
        /** Provides the state. */
        ViewState provideViewState();
    }

    /** A POJO for the desired state of {@link KeyguardStatusBarView}. */
    static class ViewState {
        final float mAlpha;

        ViewState(float alpha) {
            this.mAlpha = alpha;
        }
    }
}
+41 −81
Original line number Diff line number Diff line
@@ -180,7 +180,6 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.inject.Inject;
import javax.inject.Provider;
@@ -261,17 +260,6 @@ public class NotificationPanelViewController extends PanelViewController {
    private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
    private static final Rect EMPTY_RECT = new Rect();

    private final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT = AnimatableProperty.from(
            "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
            (notificationPanelView, aFloat) -> setKeyguardHeadsUpShowingAmount(aFloat),
            (Function<NotificationPanelView, Float>) notificationPanelView ->
                    getKeyguardHeadsUpShowingAmount(),
            R.id.keyguard_hun_animator_tag, R.id.keyguard_hun_animator_end_tag,
            R.id.keyguard_hun_animator_start_tag);
    private static final AnimationProperties
            KEYGUARD_HUN_PROPERTIES =
            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

    private final LayoutInflater mLayoutInflater;
    private final PowerManager mPowerManager;
    private final AccessibilityManager mAccessibilityManager;
@@ -360,7 +348,6 @@ public class NotificationPanelViewController extends PanelViewController {
    private FlingAnimationUtils mFlingAnimationUtils;
    private int mStatusBarMinHeight;
    private int mStatusBarHeaderHeightKeyguard;
    private int mNotificationsHeaderCollideDistance;
    private float mOverStretchAmount;
    private float mDownX;
    private float mDownY;
@@ -494,8 +481,6 @@ public class NotificationPanelViewController extends PanelViewController {
    private int mDarkIconSize;
    private int mHeadsUpInset;
    private boolean mHeadsUpPinnedMode;
    private float mKeyguardHeadsUpShowingAmount = 0.0f;
    private boolean mShowingKeyguardHeadsUp;
    private boolean mAllowExpandForSmallExpansion;
    private Runnable mExpandAfterLayoutRunnable;

@@ -638,17 +623,6 @@ public class NotificationPanelViewController extends PanelViewController {
        }
    };

    private final KeyguardStatusBarViewController.ViewStateProvider mViewStateProvider =
            new KeyguardStatusBarViewController.ViewStateProvider() {
                @Override
                public KeyguardStatusBarViewController.ViewState provideViewState() {
                    float alphaQsExpansion = 1 - Math.min(1, computeQsExpansionFraction() * 2);
                    float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
                            * (1.0f - mKeyguardHeadsUpShowingAmount);
                    return new KeyguardStatusBarViewController.ViewState(newAlpha);
                }
            };

    @Inject
    public NotificationPanelViewController(NotificationPanelView view,
            @Main Resources resources,
@@ -839,7 +813,7 @@ public class NotificationPanelViewController extends PanelViewController {
        mKeyguardStatusBarViewController =
                mKeyguardStatusBarViewComponentFactory.build(
                        mKeyguardStatusBar,
                        mViewStateProvider)
                        mNotificationPanelViewStateProvider)
                        .getKeyguardStatusBarViewController();
        mKeyguardStatusBarViewController.init();

@@ -873,7 +847,7 @@ public class NotificationPanelViewController extends PanelViewController {
        mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
            @Override
            public void onFullyHiddenChanged(boolean isFullyHidden) {
                updateKeyguardStatusBarForHeadsUp();
                mKeyguardStatusBarViewController.updateForHeadsUp();
            }

            @Override
@@ -910,8 +884,6 @@ public class NotificationPanelViewController extends PanelViewController {
        mStatusBarHeaderHeightKeyguard = mResources.getDimensionPixelSize(
                R.dimen.status_bar_header_height_keyguard);
        mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
        mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize(
                R.dimen.header_notifications_collide_distance);
        mClockPositionAlgorithm.loadDimens(mResources);
        mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
        mPositionMinSideMargin = mResources.getDimensionPixelSize(
@@ -2922,30 +2894,6 @@ public class NotificationPanelViewController extends PanelViewController {
        return Math.min(0, translation);
    }

    /**
     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
     * during swiping up
     */
    private float getKeyguardContentsAlpha() {
        float alpha;
        if (mBarState == KEYGUARD) {

            // When on Keyguard, we hide the header as soon as we expanded close enough to the
            // header
            alpha =
                    getExpandedHeight() / (mKeyguardStatusBar.getHeight()
                            + mNotificationsHeaderCollideDistance);
        } else {

            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
            // soon as we start translating the stack.
            alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
        }
        alpha = MathUtils.saturate(alpha);
        alpha = (float) Math.pow(alpha, 0.75);
        return alpha;
    }

    private void updateKeyguardBottomAreaAlpha() {
        // There are two possible panel expansion behaviors:
        // • User dragging up to unlock: we want to fade out as quick as possible
@@ -3241,31 +3189,6 @@ public class NotificationPanelViewController extends PanelViewController {
        mPanelAlphaEndAction = r;
    }

    private void updateKeyguardStatusBarForHeadsUp() {
        boolean
                showingKeyguardHeadsUp =
                mKeyguardShowing && mHeadsUpAppearanceController.shouldBeVisible();
        if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
            mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
            if (mKeyguardShowing) {
                PropertyAnimator.setProperty(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
                        showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
                        true /* animate */);
            } else {
                PropertyAnimator.applyImmediately(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
            }
        }
    }

    private void setKeyguardHeadsUpShowingAmount(float amount) {
        mKeyguardHeadsUpShowingAmount = amount;
        mKeyguardStatusBarViewController.updateViewState();
    }

    private float getKeyguardHeadsUpShowingAmount() {
        return mKeyguardHeadsUpShowingAmount;
    }

    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
        mHeadsUpAnimatingAway = headsUpAnimatingAway;
        mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
@@ -4245,7 +4168,7 @@ public class NotificationPanelViewController extends PanelViewController {
            updateGestureExclusionRect();
            mHeadsUpPinnedMode = inPinnedMode;
            updateHeadsUpVisibility();
            updateKeyguardStatusBarForHeadsUp();
            mKeyguardStatusBarViewController.updateForHeadsUp();
        }

        @Override
@@ -4404,7 +4327,7 @@ public class NotificationPanelViewController extends PanelViewController {
                    }
                }
            }
            updateKeyguardStatusBarForHeadsUp();
            mKeyguardStatusBarViewController.updateForHeadsUp();
            if (keyguardShowing) {
                updateDozingVisibilities(false /* animate */);
            }
@@ -4429,6 +4352,43 @@ public class NotificationPanelViewController extends PanelViewController {
        }
    }

    /**
     * An interface that provides the current state of the notification panel and related views,
     * which is needed to calculate {@link KeyguardStatusBarView}'s state in
     * {@link KeyguardStatusBarViewController}.
     */
    public interface NotificationPanelViewStateProvider {
        /** Returns the expanded height of the panel view. */
        float getPanelViewExpandedHeight();
        /** Returns the fraction of QS that's expanded. */
        float getQsExpansionFraction();
        /**
         * Returns true if heads up should be visible.
         *
         * TODO(b/138786270): If HeadsUpAppearanceController was injectable, we could inject it into
         * {@link KeyguardStatusBarViewController} and remove this method.
         */
        boolean shouldHeadsUpBeVisible();
    }

    private final NotificationPanelViewStateProvider mNotificationPanelViewStateProvider =
            new NotificationPanelViewStateProvider() {
                @Override
                public float getPanelViewExpandedHeight() {
                    return getExpandedHeight();
                }

                @Override
                public float getQsExpansionFraction() {
                    return computeQsExpansionFraction();
                }

                @Override
                public boolean shouldHeadsUpBeVisible() {
                    return mHeadsUpAppearanceController.shouldBeVisible();
                }
            };

    /**
     * Reconfigures the shade to show the AOD UI (clock, smartspace, etc). This is called by the
     * screen off animation controller in order to animate in AOD without "actually" fully switching
+92 −6
Original line number Diff line number Diff line
@@ -86,15 +86,14 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
    @Mock
    private SysuiStatusBarStateController mStatusBarStateController;

    private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
    private KeyguardStatusBarView mKeyguardStatusBarView;
    private KeyguardStatusBarViewController mController;

    private float mAlpha = 0.5f;
    private final KeyguardStatusBarViewController.ViewStateProvider mViewStateProvider =
            () -> new KeyguardStatusBarViewController.ViewState(mAlpha);

    @Before
    public void setup() throws Exception {
        mNotificationPanelViewStateProvider = new TestNotificationPanelViewStateProvider();

        MockitoAnnotations.initMocks(this);

        allowTestableLooperAsMainThread();
@@ -114,7 +113,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
                mStatusBarIconController,
                new StatusBarIconController.TintedIconManager.Factory(mFeatureFlags),
                mBatteryMeterViewController,
                mViewStateProvider,
                mNotificationPanelViewStateProvider,
                mKeyguardStateController,
                mKeyguardBypassController,
                mKeyguardUpdateMonitor,
@@ -210,7 +209,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {

    @Test
    public void updateViewState_notKeyguardState_nothingUpdated() {
        mAlpha = 0.255f;
        mController.onViewAttached();
        updateStateToNotKeyguard();

@@ -264,8 +262,59 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
    }

    @Test
    public void updateViewState_panelExpandedHeightZero_viewHidden() {
        mController.onViewAttached();
        updateStateToKeyguard();

        mNotificationPanelViewStateProvider.setPanelViewExpandedHeight(0);

        mController.updateViewState();

        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
    }

    @Test
    public void updateViewState_qsExpansionOne_viewHidden() {
        mController.onViewAttached();
        updateStateToKeyguard();

        mNotificationPanelViewStateProvider.setQsExpansionFraction(1f);

        mController.updateViewState();

        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
    }

    // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.

    @Test
    public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
        mController.onViewAttached();
        updateStateToKeyguard();
        mKeyguardStatusBarView.setVisibility(View.VISIBLE);

        mNotificationPanelViewStateProvider.setShouldHeadsUpBeVisible(true);
        mController.updateForHeadsUp(/* animate= */ false);

        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
    }

    @Test
    public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
        mController.onViewAttached();
        updateStateToKeyguard();

        // Start with the opposite state.
        mNotificationPanelViewStateProvider.setShouldHeadsUpBeVisible(true);
        mController.updateForHeadsUp(/* animate= */ false);

        mNotificationPanelViewStateProvider.setShouldHeadsUpBeVisible(false);
        mController.updateForHeadsUp(/* animate= */ false);

        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
    }

    private void updateStateToNotKeyguard() {
        updateStatusBarState(SHADE);
    }
@@ -295,4 +344,41 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {

        callback.onFinishedGoingToSleep(0);
    }

    private static class TestNotificationPanelViewStateProvider
            implements NotificationPanelViewController.NotificationPanelViewStateProvider {

        TestNotificationPanelViewStateProvider() {}

        private float mPanelViewExpandedHeight = 100f;
        private float mQsExpansionFraction = 0f;
        private boolean mShouldHeadsUpBeVisible = false;

        @Override
        public float getPanelViewExpandedHeight() {
            return mPanelViewExpandedHeight;
        }

        @Override
        public float getQsExpansionFraction() {
            return mQsExpansionFraction;
        }

        @Override
        public boolean shouldHeadsUpBeVisible() {
            return mShouldHeadsUpBeVisible;
        }

        public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
            this.mPanelViewExpandedHeight = panelViewExpandedHeight;
        }

        public void setQsExpansionFraction(float qsExpansionFraction) {
            this.mQsExpansionFraction = qsExpansionFraction;
        }

        public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
            this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
        }
    }
}