Merge "Quick affordance gesture polish and haptics." into tm-qpr-dev

This commit is contained in:
Ale Nijamkin
2022-12-22 20:51:47 +00:00
committed by Android (Google) Code Review
4 changed files with 129 additions and 24 deletions

View File

@@ -2751,7 +2751,7 @@
Error message shown when a button should be pressed and held to activate it, usually shown when Error message shown when a button should be pressed and held to activate it, usually shown when
the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32]. the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32].
--> -->
<string name="keyguard_affordance_press_too_short">Press and hold to activate</string> <string name="keyguard_affordance_press_too_short">Touch &amp; hold to open</string>
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] --> <!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_cancel">Cancel</string> <string name="rear_display_bottom_sheet_cancel">Cancel</string>

View File

@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.binder
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.drawable.Animatable2 import android.graphics.drawable.Animatable2
import android.os.VibrationEffect
import android.util.Size import android.util.Size
import android.util.TypedValue import android.util.TypedValue
import android.view.MotionEvent import android.view.MotionEvent
@@ -43,8 +44,11 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.kotlin.pairwise
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@@ -93,6 +97,7 @@ object KeyguardBottomAreaViewBinder {
view: ViewGroup, view: ViewGroup,
viewModel: KeyguardBottomAreaViewModel, viewModel: KeyguardBottomAreaViewModel,
falsingManager: FalsingManager?, falsingManager: FalsingManager?,
vibratorHelper: VibratorHelper?,
messageDisplayer: (Int) -> Unit, messageDisplayer: (Int) -> Unit,
): Binding { ): Binding {
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area) val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
@@ -118,10 +123,23 @@ object KeyguardBottomAreaViewBinder {
viewModel = buttonModel, viewModel = buttonModel,
falsingManager = falsingManager, falsingManager = falsingManager,
messageDisplayer = messageDisplayer, messageDisplayer = messageDisplayer,
vibratorHelper = vibratorHelper,
) )
} }
} }
launch {
viewModel.startButton
.map { it.isActivated }
.pairwise()
.collect { (prev, next) ->
when {
!prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
}
}
}
launch { launch {
viewModel.endButton.collect { buttonModel -> viewModel.endButton.collect { buttonModel ->
updateButton( updateButton(
@@ -129,10 +147,23 @@ object KeyguardBottomAreaViewBinder {
viewModel = buttonModel, viewModel = buttonModel,
falsingManager = falsingManager, falsingManager = falsingManager,
messageDisplayer = messageDisplayer, messageDisplayer = messageDisplayer,
vibratorHelper = vibratorHelper,
) )
} }
} }
launch {
viewModel.endButton
.map { it.isActivated }
.pairwise()
.collect { (prev, next) ->
when {
!prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
}
}
}
launch { launch {
viewModel.isOverlayContainerVisible.collect { isVisible -> viewModel.isOverlayContainerVisible.collect { isVisible ->
overlayContainer.visibility = overlayContainer.visibility =
@@ -239,6 +270,7 @@ object KeyguardBottomAreaViewBinder {
viewModel: KeyguardQuickAffordanceViewModel, viewModel: KeyguardQuickAffordanceViewModel,
falsingManager: FalsingManager?, falsingManager: FalsingManager?,
messageDisplayer: (Int) -> Unit, messageDisplayer: (Int) -> Unit,
vibratorHelper: VibratorHelper?,
) { ) {
if (!viewModel.isVisible) { if (!viewModel.isVisible) {
view.isVisible = false view.isVisible = false
@@ -312,7 +344,9 @@ object KeyguardBottomAreaViewBinder {
view.isClickable = viewModel.isClickable view.isClickable = viewModel.isClickable
if (viewModel.isClickable) { if (viewModel.isClickable) {
if (viewModel.useLongPress) { if (viewModel.useLongPress) {
view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer)) view.setOnTouchListener(
OnTouchListener(view, viewModel, messageDisplayer, vibratorHelper)
)
} else { } else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
} }
@@ -328,6 +362,7 @@ object KeyguardBottomAreaViewBinder {
private val view: View, private val view: View,
private val viewModel: KeyguardQuickAffordanceViewModel, private val viewModel: KeyguardQuickAffordanceViewModel,
private val messageDisplayer: (Int) -> Unit, private val messageDisplayer: (Int) -> Unit,
private val vibratorHelper: VibratorHelper?,
) : View.OnTouchListener { ) : View.OnTouchListener {
private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong() private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
@@ -376,25 +411,38 @@ object KeyguardBottomAreaViewBinder {
true true
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
cancel(
onAnimationEnd =
if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) { if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short) Runnable {
val shakeAnimator = messageDisplayer.invoke(
ObjectAnimator.ofFloat( R.string.keyguard_affordance_press_too_short
view, )
"translationX", val amplitude =
0f,
view.context.resources view.context.resources
.getDimensionPixelSize( .getDimensionPixelSize(
R.dimen.keyguard_affordance_shake_amplitude R.dimen.keyguard_affordance_shake_amplitude
) )
.toFloat(), .toFloat()
0f, val shakeAnimator =
ObjectAnimator.ofFloat(
view,
"translationX",
-amplitude / 2,
amplitude / 2,
) )
shakeAnimator.duration = 300 shakeAnimator.duration =
shakeAnimator.interpolator = CycleInterpolator(5f) ShakeAnimationDuration.inWholeMilliseconds
shakeAnimator.interpolator =
CycleInterpolator(ShakeAnimationCycles)
shakeAnimator.start() shakeAnimator.start()
vibratorHelper?.vibrate(Vibrations.Shake)
} }
cancel() } else {
null
}
)
true true
} }
MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_CANCEL -> {
@@ -405,11 +453,11 @@ object KeyguardBottomAreaViewBinder {
} }
} }
private fun cancel() { private fun cancel(onAnimationEnd: Runnable? = null) {
downTimestamp = 0L downTimestamp = 0L
longPressAnimator?.cancel() longPressAnimator?.cancel()
longPressAnimator = null longPressAnimator = null
view.animate().scaleX(1f).scaleY(1f) view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
} }
companion object { companion object {
@@ -461,4 +509,58 @@ object KeyguardBottomAreaViewBinder {
val indicationTextSizePx: Int, val indicationTextSizePx: Int,
val buttonSizePx: Size, val buttonSizePx: Size,
) )
private val ShakeAnimationDuration = 300.milliseconds
private val ShakeAnimationCycles = 5f
object Vibrations {
private const val SmallVibrationScale = 0.3f
private const val BigVibrationScale = 0.6f
val Shake =
VibrationEffect.startComposition()
.apply {
val vibrationDelayMs =
(ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
.toInt()
val vibrationCount = ShakeAnimationCycles.toInt() * 2
repeat(vibrationCount) {
addPrimitive(
VibrationEffect.Composition.PRIMITIVE_TICK,
SmallVibrationScale,
vibrationDelayMs,
)
}
}
.compose()
val Activated =
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_TICK,
BigVibrationScale,
0,
)
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
0.1f,
0,
)
.compose()
val Deactivated =
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_TICK,
BigVibrationScale,
0,
)
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
0.1f,
0,
)
.compose()
}
} }

View File

@@ -1400,8 +1400,8 @@ public final class NotificationPanelViewController implements Dumpable {
mFalsingManager, mFalsingManager,
mLockIconViewController, mLockIconViewController,
stringResourceId -> stringResourceId ->
mKeyguardIndicationController.showTransientIndication(stringResourceId) mKeyguardIndicationController.showTransientIndication(stringResourceId),
); mVibratorHelper);
} }
@VisibleForTesting @VisibleForTesting

View File

@@ -30,6 +30,7 @@ import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
/** /**
* Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -65,12 +66,14 @@ constructor(
falsingManager: FalsingManager? = null, falsingManager: FalsingManager? = null,
lockIconViewController: LockIconViewController? = null, lockIconViewController: LockIconViewController? = null,
messageDisplayer: MessageDisplayer? = null, messageDisplayer: MessageDisplayer? = null,
vibratorHelper: VibratorHelper? = null,
) { ) {
binding = binding =
bind( bind(
this, this,
viewModel, viewModel,
falsingManager, falsingManager,
vibratorHelper,
) { ) {
messageDisplayer?.display(it) messageDisplayer?.display(it)
} }