diff options
4 files changed, 129 insertions, 24 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ce3084c6046c..2eb58b9c98ec 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2870,7 +2870,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> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index ae8edfece4cb..b19795c38579 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -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,22 +123,48 @@ 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( view = endButton, 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 = if (isVisible) { @@ -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() + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 31543dfcb540..3e9d89a18c37 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1374,8 +1374,8 @@ public final class NotificationPanelViewController implements Dumpable { mFalsingManager, mLockIconViewController, stringResourceId -> - mKeyguardIndicationController.showTransientIndication(stringResourceId) - ); + mKeyguardIndicationController.showTransientIndication(stringResourceId), + mVibratorHelper); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt index 2ce116394236..e4227dce94e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt @@ -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) } |