Merge "Quick affordance gesture polish and haptics." into tm-qpr-dev am: 1746734f5d
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20808866 Change-Id: Ica48ca6117919ecf9e392874428df6c57c3a452e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -2751,7 +2751,7 @@
|
||||
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].
|
||||
-->
|
||||
<string name="keyguard_affordance_press_too_short">Press and hold to activate</string>
|
||||
<string name="keyguard_affordance_press_too_short">Touch & hold to open</string>
|
||||
|
||||
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
|
||||
<string name="rear_display_bottom_sheet_cancel">Cancel</string>
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.binder
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.drawable.Animatable2
|
||||
import android.os.VibrationEffect
|
||||
import android.util.Size
|
||||
import android.util.TypedValue
|
||||
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.lifecycle.repeatWhenAttached
|
||||
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.sqrt
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -93,6 +97,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
view: ViewGroup,
|
||||
viewModel: KeyguardBottomAreaViewModel,
|
||||
falsingManager: FalsingManager?,
|
||||
vibratorHelper: VibratorHelper?,
|
||||
messageDisplayer: (Int) -> Unit,
|
||||
): Binding {
|
||||
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
|
||||
@@ -118,10 +123,23 @@ object KeyguardBottomAreaViewBinder {
|
||||
viewModel = buttonModel,
|
||||
falsingManager = falsingManager,
|
||||
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 {
|
||||
viewModel.endButton.collect { buttonModel ->
|
||||
updateButton(
|
||||
@@ -129,10 +147,23 @@ object KeyguardBottomAreaViewBinder {
|
||||
viewModel = buttonModel,
|
||||
falsingManager = falsingManager,
|
||||
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 {
|
||||
viewModel.isOverlayContainerVisible.collect { isVisible ->
|
||||
overlayContainer.visibility =
|
||||
@@ -239,6 +270,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
viewModel: KeyguardQuickAffordanceViewModel,
|
||||
falsingManager: FalsingManager?,
|
||||
messageDisplayer: (Int) -> Unit,
|
||||
vibratorHelper: VibratorHelper?,
|
||||
) {
|
||||
if (!viewModel.isVisible) {
|
||||
view.isVisible = false
|
||||
@@ -312,7 +344,9 @@ object KeyguardBottomAreaViewBinder {
|
||||
view.isClickable = viewModel.isClickable
|
||||
if (viewModel.isClickable) {
|
||||
if (viewModel.useLongPress) {
|
||||
view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer))
|
||||
view.setOnTouchListener(
|
||||
OnTouchListener(view, viewModel, messageDisplayer, vibratorHelper)
|
||||
)
|
||||
} else {
|
||||
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
|
||||
}
|
||||
@@ -328,6 +362,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
private val view: View,
|
||||
private val viewModel: KeyguardQuickAffordanceViewModel,
|
||||
private val messageDisplayer: (Int) -> Unit,
|
||||
private val vibratorHelper: VibratorHelper?,
|
||||
) : View.OnTouchListener {
|
||||
|
||||
private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
|
||||
@@ -376,25 +411,38 @@ object KeyguardBottomAreaViewBinder {
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
|
||||
messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
|
||||
val shakeAnimator =
|
||||
ObjectAnimator.ofFloat(
|
||||
view,
|
||||
"translationX",
|
||||
0f,
|
||||
view.context.resources
|
||||
.getDimensionPixelSize(
|
||||
R.dimen.keyguard_affordance_shake_amplitude
|
||||
cancel(
|
||||
onAnimationEnd =
|
||||
if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
|
||||
Runnable {
|
||||
messageDisplayer.invoke(
|
||||
R.string.keyguard_affordance_press_too_short
|
||||
)
|
||||
.toFloat(),
|
||||
0f,
|
||||
)
|
||||
shakeAnimator.duration = 300
|
||||
shakeAnimator.interpolator = CycleInterpolator(5f)
|
||||
shakeAnimator.start()
|
||||
}
|
||||
cancel()
|
||||
val amplitude =
|
||||
view.context.resources
|
||||
.getDimensionPixelSize(
|
||||
R.dimen.keyguard_affordance_shake_amplitude
|
||||
)
|
||||
.toFloat()
|
||||
val shakeAnimator =
|
||||
ObjectAnimator.ofFloat(
|
||||
view,
|
||||
"translationX",
|
||||
-amplitude / 2,
|
||||
amplitude / 2,
|
||||
)
|
||||
shakeAnimator.duration =
|
||||
ShakeAnimationDuration.inWholeMilliseconds
|
||||
shakeAnimator.interpolator =
|
||||
CycleInterpolator(ShakeAnimationCycles)
|
||||
shakeAnimator.start()
|
||||
|
||||
vibratorHelper?.vibrate(Vibrations.Shake)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
@@ -405,11 +453,11 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancel() {
|
||||
private fun cancel(onAnimationEnd: Runnable? = null) {
|
||||
downTimestamp = 0L
|
||||
longPressAnimator?.cancel()
|
||||
longPressAnimator = null
|
||||
view.animate().scaleX(1f).scaleY(1f)
|
||||
view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -461,4 +509,58 @@ object KeyguardBottomAreaViewBinder {
|
||||
val indicationTextSizePx: Int,
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1400,8 +1400,8 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
mFalsingManager,
|
||||
mLockIconViewController,
|
||||
stringResourceId ->
|
||||
mKeyguardIndicationController.showTransientIndication(stringResourceId)
|
||||
);
|
||||
mKeyguardIndicationController.showTransientIndication(stringResourceId),
|
||||
mVibratorHelper);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -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.viewmodel.KeyguardBottomAreaViewModel
|
||||
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
|
||||
@@ -65,12 +66,14 @@ constructor(
|
||||
falsingManager: FalsingManager? = null,
|
||||
lockIconViewController: LockIconViewController? = null,
|
||||
messageDisplayer: MessageDisplayer? = null,
|
||||
vibratorHelper: VibratorHelper? = null,
|
||||
) {
|
||||
binding =
|
||||
bind(
|
||||
this,
|
||||
viewModel,
|
||||
falsingManager,
|
||||
vibratorHelper,
|
||||
) {
|
||||
messageDisplayer?.display(it)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user