[Motion] Split-shade expansion on LS: move keyguard views

Moves the keyguard views (clock, smart space) vertically at the same
rate as the media view is moving.

Fixes: 222034043
Test: Manually
Change-Id: I66e87574d2664b8d5c3d4e0cf248cfcad8924903
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 31f466f..087817f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -113,6 +113,22 @@
         }
     }
 
+    /** Sets a translationY value on every child view except for the media view. */
+    public void setChildrenTranslationYExcludingMediaView(float translationY) {
+        setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
+    }
+
+    /** Sets a translationY value on every view except for the views in the provided set. */
+    private void setChildrenTranslationYExcluding(float translationY, Set<View> excludedViews) {
+        for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) {
+            final View child = mStatusViewContainer.getChildAt(i);
+
+            if (!excludedViews.contains(child)) {
+                child.setTranslationY(translationY);
+            }
+        }
+    }
+
     public float getChildrenAlphaExcludingSmartSpace() {
         return mChildrenAlphaExcludingSmartSpace;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index af3da9f..14c9cb2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -137,6 +137,13 @@
     }
 
     /**
+     * Sets a translationY on the views on the keyguard, except on the media view.
+     */
+    public void setTranslationYExcludingMedia(float translationY) {
+        mView.setChildrenTranslationYExcludingMediaView(translationY);
+    }
+
+    /**
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 18b6699..7a4dee2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -292,6 +292,18 @@
     }
 
     /**
+     * Returns the amount of translationY of the media container, during the current guided
+     * transformation, if running. If there is no guided transformation running, it will return 0.
+     */
+    fun getGuidedTransformationTranslationY(): Int {
+        if (!isCurrentlyInGuidedTransformation()) {
+            return -1
+        }
+        val startHost = getHost(previousLocation) ?: return 0
+        return targetBounds.top - startHost.currentBounds.top
+    }
+
+    /**
      * Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
      * we wouldn't want to transition in that case.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 1ab0345..ab4d0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -406,6 +406,7 @@
 
                     mediaHierarchyManager.setTransitionToFullShadeAmount(field)
                     transitionToShadeAmountCommon(field)
+                    transitionToShadeAmountKeyguard(field)
                 }
             }
         }
@@ -420,11 +421,6 @@
         val scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
         scrimController.setTransitionToFullShadeProgress(scrimProgress)
 
-        // Fade out all content only visible on the lockscreen
-        val npvcProgress =
-            MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
-        notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - npvcProgress)
-
         if (depthControllerTransitionDistance > 0) {
             val depthProgress =
                 MathUtils.saturate(dragDownAmount / depthControllerTransitionDistance)
@@ -438,6 +434,22 @@
         centralSurfaces.setTransitionToFullShadeProgress(statusBarProgress)
     }
 
+    private fun transitionToShadeAmountKeyguard(dragDownAmount: Float) {
+        // Fade out all content only visible on the lockscreen
+        val keyguardAlphaProgress =
+            MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
+        val keyguardAlpha = 1f - keyguardAlphaProgress
+        val keyguardTranslationY = if (useSplitShade) {
+            // On split-shade, the translationY of the keyguard should stay in sync with the
+            // translation of media.
+            mediaHierarchyManager.getGuidedTransformationTranslationY()
+        } else {
+            0
+        }
+        notificationPanelController
+            .setKeyguardTransitionProgress(keyguardAlpha, keyguardTranslationY)
+    }
+
     private fun setDragDownAmountAnimated(
         target: Float,
         delay: Long = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 0a2ea4c..cbdf5c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -616,6 +616,11 @@
      */
     private float mKeyguardOnlyContentAlpha = 1.0f;
 
+    /**
+     * The translationY of the views which only show on the keyguard but in shade / shade locked.
+     */
+    private int mKeyguardOnlyTransitionTranslationY = 0;
+
     private float mUdfpsMaxYBurnInOffset;
 
     /**
@@ -1575,6 +1580,8 @@
     private void updateClock() {
         float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
         mKeyguardStatusViewController.setAlpha(alpha);
+        mKeyguardStatusViewController
+                .setTranslationYExcludingMedia(mKeyguardOnlyTransitionTranslationY);
         if (mKeyguardQsUserSwitchController != null) {
             mKeyguardQsUserSwitchController.setAlpha(alpha);
         }
@@ -2732,11 +2739,12 @@
     }
 
     /**
-     * Set the alpha of the keyguard elements which only show on the lockscreen, but not in
-     * shade locked / shade. This is used when dragging down to the full shade.
+     * Set the alpha and translationY of the keyguard elements which only show on the lockscreen,
+     * but not in shade locked / shade. This is used when dragging down to the full shade.
      */
-    public void setKeyguardOnlyContentAlpha(float keyguardAlpha) {
+    public void setKeyguardTransitionProgress(float keyguardAlpha, int keyguardTranslationY) {
         mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha);
+        mKeyguardOnlyTransitionTranslationY = keyguardTranslationY;
         if (mBarState == KEYGUARD) {
             // If the animator is running, it's already fading out the content and this is a reset
             mBottomAreaShadeAlpha = mKeyguardOnlyContentAlpha;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 2fc9122..1753157 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -112,4 +112,13 @@
         mKeyguardUpdateMonitorCallbackCaptor.getValue().onUserSwitchComplete(0);
         verify(mKeyguardClockSwitchController).refreshFormat();
     }
+
+    @Test
+    public void setTranslationYExcludingMedia_forwardsCallToView() {
+        float translationY = 123f;
+
+        mController.setTranslationYExcludingMedia(translationY);
+
+        verify(mKeyguardStatusView).setChildrenTranslationYExcludingMediaView(translationY);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
new file mode 100644
index 0000000..ce44f4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -0,0 +1,55 @@
+package com.android.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.children
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusViewTest : SysuiTestCase() {
+
+    private lateinit var keyguardStatusView: KeyguardStatusView
+    private val mediaView: View
+        get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+    private val statusViewContainer: ViewGroup
+        get() = keyguardStatusView.findViewById(R.id.status_view_container)
+    private val childrenExcludingMedia
+        get() = statusViewContainer.children.filter { it != mediaView }
+
+    @Before
+    fun setUp() {
+        keyguardStatusView = LayoutInflater.from(context)
+                .inflate(R.layout.keyguard_status_view, /* root= */ null) as KeyguardStatusView
+    }
+
+    @Test
+    fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
+        val translationY = 1234f
+
+        keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+
+        assertThat(mediaView.translationY).isEqualTo(0)
+    }
+
+    @Test
+    fun setChildrenTranslationYExcludingMediaView_childrenAreTranslated() {
+        val translationY = 1234f
+
+        keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+
+        childrenExcludingMedia.forEach {
+            assertThat(it.translationY).isEqualTo(translationY)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index b359ae5..8e201b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -117,9 +117,9 @@
                 dreamOverlayStateController)
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
-        setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
-        setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
-        setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS)
+        setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
+        setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
+        setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
         `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         `when`(mediaCarouselController.mediaCarouselScrollHandler)
                 .thenReturn(mediaCarouselScrollHandler)
@@ -130,9 +130,9 @@
         clearInvocations(mediaCarouselController)
     }
 
-    private fun setupHost(host: MediaHost, location: Int) {
+    private fun setupHost(host: MediaHost, location: Int, top: Int) {
         `when`(host.location).thenReturn(location)
-        `when`(host.currentBounds).thenReturn(Rect())
+        `when`(host.currentBounds).thenReturn(Rect(0, top, 0, top))
         `when`(host.hostView).thenReturn(uniqueObjectHostView)
         `when`(host.visible).thenReturn(true)
         mediaHiearchyManager.register(host)
@@ -257,6 +257,20 @@
         verify(mediaCarouselController).closeGuts()
     }
 
+    @Test
+    fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() {
+        assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
+    }
+
+    @Test
+    fun getGuidedTransformationTranslationY_inGuidedTransformation_returnsCurrentTranslation() {
+        enterGuidedTransformation()
+
+        val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
+        assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY())
+                .isEqualTo(expectedTranslation)
+    }
+
     private fun enableSplitShade() {
         context.getOrCreateTestableResources().addOverride(
             R.bool.config_use_split_notification_shade, true
@@ -284,4 +298,16 @@
     private fun expandQS() {
         mediaHiearchyManager.qsExpansion = 1.0f
     }
+
+    private fun enterGuidedTransformation() {
+        mediaHiearchyManager.qsExpansion = 1.0f
+        goToLockscreen()
+        mediaHiearchyManager.setTransitionToFullShadeAmount(123f)
+    }
+
+    companion object {
+        private const val QQS_TOP = 123
+        private const val QS_TOP = 456
+        private const val LOCKSCREEN_TOP = 789
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 067caa9..64a0a23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -44,6 +44,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.ArgumentMatchers.eq
 
 private fun <T> anyObject(): T {
     return Mockito.anyObject<T>()
@@ -260,6 +261,49 @@
     }
 
     @Test
+    fun setDragAmount_setsKeyguardTransitionProgress() {
+        transitionController.dragDownAmount = 10f
+
+        verify(notificationPanelController).setKeyguardTransitionProgress(anyFloat(), anyInt())
+    }
+
+    @Test
+    fun setDragAmount_setsKeyguardAlphaBasedOnDistance() {
+        val alphaDistance = context.resources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance)
+        transitionController.dragDownAmount = 10f
+
+        val expectedAlpha = 1 - 10f / alphaDistance
+        verify(notificationPanelController)
+                .setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
+    }
+
+    @Test
+    fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() {
+        val mediaTranslationY = 123
+        disableSplitShade()
+        whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+                .thenReturn(mediaTranslationY)
+
+        transitionController.dragDownAmount = 10f
+
+        verify(notificationPanelController).setKeyguardTransitionProgress(anyFloat(), eq(0))
+    }
+
+    @Test
+    fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() {
+        val mediaTranslationY = 123
+        enableSplitShade()
+        whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+                .thenReturn(mediaTranslationY)
+
+        transitionController.dragDownAmount = 10f
+
+        verify(notificationPanelController)
+                .setKeyguardTransitionProgress(anyFloat(), eq(mediaTranslationY))
+    }
+
+    @Test
     fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
         transitionController.dragDownAmount = 10f
 
@@ -276,8 +320,16 @@
     }
 
     private fun enableSplitShade() {
+        setSplitShadeEnabled(true)
+    }
+
+    private fun disableSplitShade() {
+        setSplitShadeEnabled(false)
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
         context.getOrCreateTestableResources().addOverride(
-            R.bool.config_use_split_notification_shade, true
+                R.bool.config_use_split_notification_shade, enabled
         )
         configurationController.notifyConfigurationChanged()
     }