Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1746734f authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

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

parents 77bef7e1 667c4952
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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 &amp; 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>
+123 −21
Original line number Diff line number Diff line
@@ -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 -> {
                    cancel(
                        onAnimationEnd =
                            if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
                        messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
                        val shakeAnimator =
                            ObjectAnimator.ofFloat(
                                view,
                                "translationX",
                                0f,
                                Runnable {
                                    messageDisplayer.invoke(
                                        R.string.keyguard_affordance_press_too_short
                                    )
                                    val amplitude =
                                        view.context.resources
                                            .getDimensionPixelSize(
                                                R.dimen.keyguard_affordance_shake_amplitude
                                            )
                                    .toFloat(),
                                0f,
                                            .toFloat()
                                    val shakeAnimator =
                                        ObjectAnimator.ofFloat(
                                            view,
                                            "translationX",
                                            -amplitude / 2,
                                            amplitude / 2,
                                        )
                        shakeAnimator.duration = 300
                        shakeAnimator.interpolator = CycleInterpolator(5f)
                                    shakeAnimator.duration =
                                        ShakeAnimationDuration.inWholeMilliseconds
                                    shakeAnimator.interpolator =
                                        CycleInterpolator(ShakeAnimationCycles)
                                    shakeAnimator.start()

                                    vibratorHelper?.vibrate(Vibrations.Shake)
                                }
                    cancel()
                            } 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()
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -1400,8 +1400,8 @@ public final class NotificationPanelViewController implements Dumpable {
                mFalsingManager,
                mLockIconViewController,
                stringResourceId ->
                        mKeyguardIndicationController.showTransientIndication(stringResourceId)
        );
                        mKeyguardIndicationController.showTransientIndication(stringResourceId),
                mVibratorHelper);
    }

    @VisibleForTesting
+3 −0
Original line number Diff line number Diff line
@@ -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)
            }