Pattern/PIN view: only update layout if posture different

Layout of Pattern or Pin view was updated whenever posture changed, now it only updates if the change leads to a different state. Primarily done to reduce jank.

Bug: 290413053
Test: KeyguardPatternViewControllerTest, KeyguardPINViewControllerTest
Change-Id: Icb972debff74d3a8afd0944ee74a520ee4b951fc
This commit is contained in:
axfordjc
2023-07-14 10:59:43 +00:00
parent 267f49b168
commit 51056127dc
4 changed files with 102 additions and 45 deletions

View File

@@ -69,7 +69,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
(long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
0.6f /* translationScale */,
0.45f /* delayScale */, AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_linear_in));
mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -82,8 +82,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
mLastDevicePosture = posture;
updateMargins();
if (mLastDevicePosture != posture) {
mLastDevicePosture = posture;
updateMargins();
}
}
@Override

View File

@@ -104,8 +104,10 @@ public class KeyguardPatternView extends KeyguardInputView
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
mLastDevicePosture = posture;
updateMargins();
if (mLastDevicePosture != posture) {
mLastDevicePosture = posture;
updateMargins();
}
}
private void updateMargins() {

View File

@@ -76,7 +76,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@Mock private lateinit var mPostureController: DevicePostureController
@Mock private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
private lateinit var fakeFeatureFlags: FakeFeatureFlags
@@ -119,7 +119,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.onViewAttached()
assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
@@ -131,15 +131,20 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(mPostureController).addCallback(postureCallbackCaptor.capture())
val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is now in posture state DEVICE_POSTURE_OPENED
assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline())
// Simulate posture change to same state with callback
assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is still in posture state DEVICE_POSTURE_OPENED
assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
}
private fun getPatternTopGuideline(): Float {
@@ -150,7 +155,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
}
private fun getExpectedTopGuideline(): Float {
private fun getHalfOpenedBouncerHeightRatio(): Float {
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}

View File

@@ -19,6 +19,8 @@ package com.android.keyguard
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +34,8 @@ import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +55,10 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPinViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardPinView: KeyguardPINView
private lateinit var objectKeyguardPINView: KeyguardPINView
@Mock private lateinit var mockKeyguardPinView: KeyguardPINView
@Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
@@ -83,64 +90,73 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Mock lateinit var deleteButton: NumPadButton
@Mock lateinit var enterButton: View
private lateinit var pinViewController: KeyguardPinViewController
@Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
.thenReturn(keyguardMessageArea)
Mockito.`when`(
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
`when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
`when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
`when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
`when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
.thenReturn(passwordTextView)
`when`(keyguardPinView.resources).thenReturn(context.resources)
`when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
`when`(mockKeyguardPinView.resources).thenReturn(context.resources)
`when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
`when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
`when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
// For posture tests:
`when`(keyguardPinView.buttons).thenReturn(arrayOf())
`when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
pinViewController =
KeyguardPinViewController(
keyguardPinView,
keyguardUpdateMonitor,
securityMode,
lockPatternUtils,
mKeyguardSecurityCallback,
keyguardMessageAreaControllerFactory,
mLatencyTracker,
liftToActivateListener,
mEmergencyButtonController,
falsingCollector,
postureController,
featureFlags
)
objectKeyguardPINView =
View.inflate(mContext, R.layout.keyguard_pin_view, null)
.findViewById(R.id.keyguard_pin_view) as KeyguardPINView
}
private fun constructPinViewController(
mKeyguardPinView: KeyguardPINView
): KeyguardPinViewController {
return KeyguardPinViewController(
mKeyguardPinView,
keyguardUpdateMonitor,
securityMode,
lockPatternUtils,
mKeyguardSecurityCallback,
keyguardMessageAreaControllerFactory,
mLatencyTracker,
liftToActivateListener,
mEmergencyButtonController,
falsingCollector,
postureController,
featureFlags
)
}
@Test
fun onViewAttached_deviceHalfFolded_propagatedToPinView() {
`when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
val pinViewController = constructPinViewController(objectKeyguardPINView)
overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
pinViewController.onViewAttached()
verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() {
`when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
val pinViewController = constructPinViewController(objectKeyguardPINView)
overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
pinViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
pinViewController.onViewAttached()
verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(postureController).addCallback(postureCallbackCaptor.capture())
@@ -148,31 +164,57 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is now in posture state DEVICE_POSTURE_OPENED
verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED)
assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to same state with callback
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is still in posture state DEVICE_POSTURE_OPENED
assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
}
private fun getPinTopGuideline(): Float {
val cs = ConstraintSet()
val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
cs.clone(container)
return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
}
private fun getHalfOpenedBouncerHeightRatio(): Float {
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
@Test
fun startAppearAnimation() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
pinViewController.startAppearAnimation()
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
pinViewController.startAppearAnimation()
verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -181,12 +223,15 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -195,7 +240,10 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun handleLockout_readsNumberOfErrorAttempts() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
pinViewController.handleAttemptLockout(0)
verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
}