Merge "Quick affordance gesture polish and haptics." into tm-qpr-dev
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
|
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 & 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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user