diff options
5 files changed, 230 insertions, 9 deletions
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3b3dc3899f33..6875cab8a7b2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1650,4 +1650,6 @@ <dimen name="qs_dialog_button_vertical_padding">8dp</dimen> <!-- The button will be 48dp tall, but the background needs to be 36dp tall --> <dimen name="qs_dialog_button_vertical_inset">6dp</dimen> + + <dimen name="keyguard_unfold_translation_x">16dp</dimen> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt index 30aec66db8e6..cb25e1a2a40e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt @@ -16,34 +16,98 @@ package com.android.keyguard +import android.content.Context +import android.view.View import android.view.ViewGroup +import com.android.systemui.R import com.android.systemui.unfold.SysUIUnfoldScope -import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import javax.inject.Inject /** - * Translates items away/towards the hinge when the device is opened/closed. + * Translates items away/towards the hinge when the device is opened/closed. This is controlled by + * the set of ids, which also dictact which direction to move and when, via a filter function. */ @SysUIUnfoldScope class KeyguardUnfoldTransition @Inject constructor( - val unfoldProgressProvider: UnfoldTransitionProgressProvider + val context: Context, + val unfoldProgressProvider: NaturalRotationUnfoldProgressProvider ) { + + companion object { + final val LEFT = -1 + final val RIGHT = 1 + } + + private val filterSplitShadeOnly = { !statusViewCentered } + private val filterNever = { true } + + private val ids = setOf( + Triple(R.id.keyguard_status_area, LEFT, filterNever), + Triple(R.id.controls_button, LEFT, filterNever), + Triple(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly), + Triple(R.id.lockscreen_clock_view, LEFT, filterNever), + Triple(R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly), + Triple(R.id.wallet_button, RIGHT, filterNever) + ) + private var parent: ViewGroup? = null + private var views = listOf<Triple<View, Int, () -> Boolean>>() + private var xTranslationMax = 0f + + /** + * Certain views only need to move if they are not currently centered + */ + var statusViewCentered = false + init { unfoldProgressProvider.addCallback( object : TransitionProgressListener { override fun onTransitionStarted() { + findViews() } override fun onTransitionProgress(progress: Float) { + translateViews(progress) } override fun onTransitionFinished() { + translateViews(1f) } } ) } + /** + * Relies on the [parent] to locate views to translate + */ fun setup(parent: ViewGroup) { + this.parent = parent + xTranslationMax = context.resources.getDimensionPixelSize( + R.dimen.keyguard_unfold_translation_x).toFloat() + } + + /** + * Manually translate views based on set direction. At the moment + * [UnfoldMoveFromCenterAnimator] exists but moves all views a dynamic distance + * from their mid-point. This code instead will only ever translate by a fixed amount. + */ + private fun translateViews(progress: Float) { + val xTrans = progress * xTranslationMax - xTranslationMax + views.forEach { + (view, direction, pred) -> if (pred()) { + view.setTranslationX(xTrans * direction) + } + } + } + + private fun findViews() { + parent?.let { p -> + views = ids.mapNotNull { + (id, direction, pred) -> p.findViewById<View>(id)?.let { + Triple(it, direction, pred) + } + } + } } } 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 dedd6daf49a6..988034f9c5fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -905,7 +905,7 @@ public class NotificationPanelViewController extends PanelViewController { } mTapAgainViewController.init(); - mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mNotificationContainerParent)); + mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView)); } @Override @@ -1150,7 +1150,7 @@ public class NotificationPanelViewController extends PanelViewController { } setKeyguardBottomAreaVisibility(mBarState, false); - mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mNotificationContainerParent)); + mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView)); } private void attachSplitShadeMediaPlayerContainer(FrameLayout container) { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index 65106f1df93c..b53ab210424f 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.unfold import com.android.keyguard.KeyguardUnfoldTransition +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController @@ -38,6 +39,7 @@ annotation class SysUIUnfoldScope * [@SysUIUnfoldScope]. Since [SysUIUnfoldComponent] depends upon: * * [Optional<UnfoldTransitionProgressProvider>] * * [Optional<ScopedUnfoldTransitionProgressProvider>] + * * [Optional<NaturalRotationProgressProvider>] * no objects will get constructed if these parameters are empty. */ @Module(subcomponents = [SysUIUnfoldComponent::class]) @@ -48,11 +50,14 @@ class SysUIUnfoldModule { @SysUISingleton fun provideSysUIUnfoldComponent( provider: Optional<UnfoldTransitionProgressProvider>, + rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>, @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>, factory: SysUIUnfoldComponent.Factory ) = - provider.flatMap { - p -> scopedProvider.map { sp -> factory.create(p, sp) } + provider.flatMap { p1 -> + rotationProvider.flatMap { p2 -> + scopedProvider.map { p3 -> factory.create(p1, p2, p3) } + } } } @@ -63,8 +68,9 @@ interface SysUIUnfoldComponent { @Subcomponent.Factory interface Factory { fun create( - @BindsInstance provider: UnfoldTransitionProgressProvider, - @BindsInstance scopedProvider: ScopedUnfoldTransitionProgressProvider + @BindsInstance p1: UnfoldTransitionProgressProvider, + @BindsInstance p2: NaturalRotationUnfoldProgressProvider, + @BindsInstance p3: ScopedUnfoldTransitionProgressProvider ): SysUIUnfoldComponent } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt new file mode 100644 index 000000000000..164f83dda9b7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2021 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.keyguard + +import android.testing.AndroidTestingRunner +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUnfoldTransition.Companion.LEFT +import com.android.keyguard.KeyguardUnfoldTransition.Companion.RIGHT +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider +import com.android.systemui.util.mockito.capture +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify + +/** + * Translates items away/towards the hinge when the device is opened/closed. This is controlled by + * the set of ids, which also dictact which direction to move and when, via a filter fn. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class KeyguardUnfoldTransitionTest : SysuiTestCase() { + + @Mock + private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider + + @Captor + private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener> + + @Mock + private lateinit var parent: ViewGroup + + private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition + private lateinit var progressListener: TransitionProgressListener + private var xTranslationMax = 0f + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + xTranslationMax = context.resources.getDimensionPixelSize( + R.dimen.keyguard_unfold_translation_x).toFloat() + + keyguardUnfoldTransition = KeyguardUnfoldTransition( + getContext(), + progressProvider + ) + + verify(progressProvider).addCallback(capture(progressListenerCaptor)) + progressListener = progressListenerCaptor.value + + keyguardUnfoldTransition.setup(parent) + keyguardUnfoldTransition.statusViewCentered = false + } + + @Test + fun onTransition_noMatchingIds() { + // GIVEN no views matching any ids + // WHEN the transition starts + progressListener.onTransitionStarted() + progressListener.onTransitionProgress(.1f) + + // THEN nothing... no exceptions + } + + @Test + fun onTransition_oneMovesLeft() { + // GIVEN one view with a matching id + val view = View(getContext()) + `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(view) + + moveAndValidate(listOf(view to LEFT)) + } + + @Test + fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() { + // GIVEN two views with a matching id + val leftView = View(getContext()) + val rightView = View(getContext()) + `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(leftView) + `when`(parent.findViewById<View>(R.id.notification_stack_scroller)).thenReturn(rightView) + + moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT)) + moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT)) + } + + @Test + fun onTransition_centeredViewDoesNotMove() { + keyguardUnfoldTransition.statusViewCentered = true + + val view = View(getContext()) + `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view) + + moveAndValidate(listOf(view to 0)) + } + + private fun moveAndValidate(list: List<Pair<View, Int>>) { + // Compare values as ints because -0f != 0f + + // WHEN the transition starts + progressListener.onTransitionStarted() + progressListener.onTransitionProgress(0f) + + list.forEach { (view, direction) -> + assertEquals((-xTranslationMax * direction).toInt(), view.getTranslationX().toInt()) + } + + // WHEN the transition progresses, translation is updated + progressListener.onTransitionProgress(.5f) + list.forEach { (view, direction) -> + assertEquals( + (-xTranslationMax / 2f * direction).toInt(), + view.getTranslationX().toInt() + ) + } + + // WHEN the transition ends, translation is completed + progressListener.onTransitionProgress(1f) + progressListener.onTransitionFinished() + list.forEach { (view, _) -> + assertEquals(0, view.getTranslationX().toInt()) + } + } +} |