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

Commit 029b4a2d authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Move KeyguardStatusView alignment animation out of NPVC

Bug: 254870485
Test: Manually checked animation
Change-Id: If9f28d40e94ddca52a8ebef4ba8025edaa4ab23f
parent 70719574
Loading
Loading
Loading
Loading
+189 −5
Original line number Diff line number Diff line
@@ -16,12 +16,37 @@

package com.android.keyguard;

import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;

import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.transition.ChangeBounds;
import android.transition.Transition;
import android.transition.TransitionListenerAdapter;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.transition.TransitionValues;
import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.VisibleForTesting;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -42,6 +67,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
    private static final boolean DEBUG = KeyguardConstants.DEBUG;
    private static final String TAG = "KeyguardStatusViewController";

    /**
     * Duration to use for the animator when the keyguard status view alignment changes, and a
     * custom clock animation is in use.
     */
    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;

    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

@@ -50,8 +81,25 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    private final ConfigurationController mConfigurationController;
    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
    private final FeatureFlags mFeatureFlags;
    private final InteractionJankMonitor mInteractionJankMonitor;
    private final Rect mClipBounds = new Rect();

    private Boolean mStatusViewCentered = true;

    private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
            new TransitionListenerAdapter() {
                @Override
                public void onTransitionCancel(Transition transition) {
                    mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
                }

                @Override
                public void onTransitionEnd(Transition transition) {
                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
                }
            };

    @Inject
    public KeyguardStatusViewController(
            KeyguardStatusView keyguardStatusView,
@@ -62,7 +110,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
            ConfigurationController configurationController,
            DozeParameters dozeParameters,
            ScreenOffAnimationController screenOffAnimationController,
            KeyguardLogger logger) {
            KeyguardLogger logger,
            FeatureFlags featureFlags,
            InteractionJankMonitor interactionJankMonitor) {
        super(keyguardStatusView);
        mKeyguardSliceViewController = keyguardSliceViewController;
        mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -71,6 +121,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
                logger.getBuffer());
        mInteractionJankMonitor = interactionJankMonitor;
        mFeatureFlags = featureFlags;
    }

    @Override
@@ -242,9 +294,141 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
        }
    }

    /** Gets the current clock controller. */
    /**
     * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
     */
    public void updateAlignment(
            ConstraintLayout notifContainerParent,
            boolean splitShadeEnabled,
            boolean shouldBeCentered,
            boolean animate) {
        if (mStatusViewCentered == shouldBeCentered) {
            return;
        }

        mStatusViewCentered = shouldBeCentered;
        if (notifContainerParent == null) {
            return;
        }

        ConstraintSet constraintSet = new ConstraintSet();
        constraintSet.clone(notifContainerParent);
        int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
        constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
        if (!animate) {
            constraintSet.applyTo(notifContainerParent);
            return;
        }

        mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
        ChangeBounds transition = new ChangeBounds();
        if (splitShadeEnabled) {
            // Excluding media from the transition on split-shade, as it doesn't transition
            // horizontally properly.
            transition.excludeTarget(R.id.status_view_media_container, true);
        }

        transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
        transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

        ClockController clock = mKeyguardClockSwitchController.getClock();
        boolean customClockAnimation = clock != null
                && clock.getConfig().getHasCustomPositionUpdatedAnimation();

        if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
            // Find the clock, so we can exclude it from this transition.
            FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);

            // The clock container can sometimes be null. If it is, just fall back to the
            // old animation rather than setting up the custom animations.
            if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
                transition.addListener(mKeyguardStatusAlignmentTransitionListener);
                TransitionManager.beginDelayedTransition(notifContainerParent, transition);
            } else {
                View clockView = clockContainerView.getChildAt(0);

                transition.excludeTarget(clockView, /* exclude= */ true);

                TransitionSet set = new TransitionSet();
                set.addTransition(transition);

                SplitShadeTransitionAdapter adapter =
                        new SplitShadeTransitionAdapter(mKeyguardClockSwitchController);

                // Use linear here, so the actual clock can pick its own interpolator.
                adapter.setInterpolator(Interpolators.LINEAR);
                adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
                adapter.addTarget(clockView);
                set.addTransition(adapter);
                set.addListener(mKeyguardStatusAlignmentTransitionListener);
                TransitionManager.beginDelayedTransition(notifContainerParent, set);
            }
        } else {
            transition.addListener(mKeyguardStatusAlignmentTransitionListener);
            TransitionManager.beginDelayedTransition(notifContainerParent, transition);
        }

        constraintSet.applyTo(notifContainerParent);
    }

    @VisibleForTesting
    static class SplitShadeTransitionAdapter extends Transition {
        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };

        private final KeyguardClockSwitchController mController;

        @VisibleForTesting
        SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) {
            mController = controller;
        }

        private void captureValues(TransitionValues transitionValues) {
            Rect boundsRect = new Rect();
            boundsRect.left = transitionValues.view.getLeft();
            boundsRect.top = transitionValues.view.getTop();
            boundsRect.right = transitionValues.view.getRight();
            boundsRect.bottom = transitionValues.view.getBottom();
            transitionValues.values.put(PROP_BOUNDS, boundsRect);
        }

        @Override
        public void captureEndValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }

        @Override
        public void captureStartValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }

        @Nullable
    public ClockController getClockController() {
        return mKeyguardClockSwitchController.getClock();
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
                @Nullable TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);

            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);

            anim.addUpdateListener(animation -> {
                ClockController clock = mController.getClock();
                if (clock == null) {
                    return;
                }

                clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
            });

            return anim;
        }

        @Override
        public String[] getTransitionProperties() {
            return TRANSITION_PROPERTIES;
        }
    }
}
+3 −158
Original line number Diff line number Diff line
@@ -22,10 +22,6 @@ import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;

import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;

import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
@@ -73,12 +69,6 @@ import android.os.Trace;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
import android.transition.ChangeBounds;
import android.transition.Transition;
import android.transition.TransitionListenerAdapter;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.transition.TransitionValues;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
@@ -100,8 +90,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;

import androidx.constraintlayout.widget.ConstraintSet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -163,8 +151,6 @@ import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.QS;
@@ -300,11 +286,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
    private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
    private static final Rect EMPTY_RECT = new Rect();
    /**
     * Duration to use for the animator when the keyguard status view alignment changes, and a
     * custom clock animation is in use.
     */
    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
    /**
     * Whether the Shade should animate to reflect Back gesture progress.
     * To minimize latency at runtime, we cache this, else we'd be reading it every time
@@ -552,8 +533,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump

    private final KeyguardMediaController mKeyguardMediaController;

    private boolean mStatusViewCentered = true;

    private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
    private final Optional<NotificationPanelUnfoldAnimationController>
            mNotificationPanelUnfoldAnimationController;
@@ -684,18 +663,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
                    step.getTransitionState() == TransitionState.RUNNING;
            };

    private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
            new TransitionListenerAdapter() {
                @Override
                public void onTransitionCancel(Transition transition) {
                    mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
                }

                @Override
                public void onTransitionEnd(Transition transition) {
                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
                }
            };
    private final ActivityStarter mActivityStarter;

    @Inject
@@ -1323,9 +1290,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
        keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
                R.layout.keyguard_status_view, mNotificationContainerParent, false);
        mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
        // When it's reinflated, this is centered by default. If it shouldn't be, this will update
        // below when resources are updated.
        mStatusViewCentered = true;
        attachSplitShadeMediaPlayerContainer(
                keyguardStatusView.findViewById(R.id.status_view_media_container));

@@ -1620,68 +1584,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump

    private void updateKeyguardStatusViewAlignment(boolean animate) {
        boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
        if (mStatusViewCentered != shouldBeCentered) {
            mStatusViewCentered = shouldBeCentered;
            ConstraintSet constraintSet = new ConstraintSet();
            constraintSet.clone(mNotificationContainerParent);
            int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
            constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
            if (animate) {
                mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
                ChangeBounds transition = new ChangeBounds();
                if (mSplitShadeEnabled) {
                    // Excluding media from the transition on split-shade, as it doesn't transition
                    // horizontally properly.
                    transition.excludeTarget(R.id.status_view_media_container, true);
                }

                transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
                transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

                ClockController clock = mKeyguardStatusViewController.getClockController();
                boolean customClockAnimation = clock != null
                        && clock.getConfig().getHasCustomPositionUpdatedAnimation();

                if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
                    // Find the clock, so we can exclude it from this transition.
                    FrameLayout clockContainerView =
                            mView.findViewById(R.id.lockscreen_clock_view_large);

                    // The clock container can sometimes be null. If it is, just fall back to the
                    // old animation rather than setting up the custom animations.
                    if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
                        transition.addListener(mKeyguardStatusAlignmentTransitionListener);
                        TransitionManager.beginDelayedTransition(
                                mNotificationContainerParent, transition);
                    } else {
                        View clockView = clockContainerView.getChildAt(0);

                        transition.excludeTarget(clockView, /* exclude= */ true);

                        TransitionSet set = new TransitionSet();
                        set.addTransition(transition);

                        SplitShadeTransitionAdapter adapter =
                                new SplitShadeTransitionAdapter(mKeyguardStatusViewController);

                        // Use linear here, so the actual clock can pick its own interpolator.
                        adapter.setInterpolator(Interpolators.LINEAR);
                        adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
                        adapter.addTarget(clockView);
                        set.addTransition(adapter);
                        set.addListener(mKeyguardStatusAlignmentTransitionListener);
                        TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
                    }
                } else {
                    transition.addListener(mKeyguardStatusAlignmentTransitionListener);
                    TransitionManager.beginDelayedTransition(
                            mNotificationContainerParent, transition);
                }
            }

            constraintSet.applyTo(mNotificationContainerParent);
        }
        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
        mKeyguardStatusViewController.updateAlignment(
                mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate);
        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
    }

    private boolean shouldKeyguardStatusViewBeCentered() {
@@ -3335,7 +3240,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
        ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
        ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
        ipw.print("mMinFraction="); ipw.println(mMinFraction);
        ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
        ipw.print("mSplitShadeFullTransitionDistance=");
        ipw.println(mSplitShadeFullTransitionDistance);
        ipw.print("mSplitShadeScrimTransitionDistance=");
@@ -5128,65 +5032,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
        }
    }

    static class SplitShadeTransitionAdapter extends Transition {
        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };

        private final KeyguardStatusViewController mController;

        SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
            mController = controller;
        }

        private void captureValues(TransitionValues transitionValues) {
            Rect boundsRect = new Rect();
            boundsRect.left = transitionValues.view.getLeft();
            boundsRect.top = transitionValues.view.getTop();
            boundsRect.right = transitionValues.view.getRight();
            boundsRect.bottom = transitionValues.view.getBottom();
            transitionValues.values.put(PROP_BOUNDS, boundsRect);
        }

        @Override
        public void captureEndValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }

        @Override
        public void captureStartValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }

        @Nullable
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
                @Nullable TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);

            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);

            anim.addUpdateListener(animation -> {
                ClockController clock = mController.getClockController();
                if (clock == null) {
                    return;
                }

                clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
            });

            return anim;
        }

        @Override
        public String[] getTransitionProperties() {
            return TRANSITION_PROPERTIES;
        }
    }

    private final class HeadsUpNotificationViewControllerImpl implements
            HeadsUpTouchHelper.HeadsUpNotificationViewController {
        @Override
+18 −31
Original line number Diff line number Diff line
@@ -16,17 +16,15 @@

package com.android.keyguard;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,26 +43,21 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class KeyguardStatusViewControllerTest extends SysuiTestCase {

    @Mock
    private KeyguardStatusView mKeyguardStatusView;
    @Mock
    private KeyguardSliceViewController mKeyguardSliceViewController;
    @Mock
    private KeyguardClockSwitchController mKeyguardClockSwitchController;
    @Mock
    private KeyguardStateController mKeyguardStateController;
    @Mock
    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    @Mock
    ConfigurationController mConfigurationController;
    @Mock
    DozeParameters mDozeParameters;
    @Mock
    ScreenOffAnimationController mScreenOffAnimationController;
    @Mock private KeyguardStatusView mKeyguardStatusView;
    @Mock private KeyguardSliceViewController mKeyguardSliceViewController;
    @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
    @Mock private KeyguardStateController mKeyguardStateController;
    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    @Mock private ConfigurationController mConfigurationController;
    @Mock private DozeParameters mDozeParameters;
    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
    @Mock private KeyguardLogger mKeyguardLogger;
    @Mock private KeyguardStatusViewController mControllerMock;
    @Mock private FeatureFlags mFeatureFlags;
    @Mock private InteractionJankMonitor mInteractionJankMonitor;

    @Captor
    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
    @Mock
    KeyguardLogger mKeyguardLogger;

    private KeyguardStatusViewController mController;

@@ -81,7 +74,9 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
                mConfigurationController,
                mDozeParameters,
                mScreenOffAnimationController,
                mKeyguardLogger);
                mKeyguardLogger,
                mFeatureFlags,
                mInteractionJankMonitor);
    }

    @Test
@@ -116,12 +111,4 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
        configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
        verify(mKeyguardClockSwitchController).onLocaleListChanged();
    }

    @Test
    public void getClock_forwardsToClockSwitch() {
        ClockController mockClock = mock(ClockController.class);
        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);

        assertEquals(mockClock, mController.getClockController());
    }
}
+4 −5

File changed and moved.

Preview size limit exceeded, changes collapsed.

+29 −4

File changed.

Preview size limit exceeded, changes collapsed.