From 8b28e859d66169a16acce89d1d7c79f1c18f7f86 Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Mon, 16 Jan 2023 15:22:07 +0000 Subject: [PATCH] [Unfold transition] Do not play haptics when hitting a timeout on folding This change makes unfold haptics play only when unfolding a foldable device. It won't play haptics after starting folding and hitting the animation timeout. It is implemented by checking that we play haptics only for the first time after receiving unfold callback from the system. Bug: 265279748 Test: atest com.android.systemui.unfold.UnfoldHapticsPlayerTest Test: manual Change-Id: I18b6c0895c76e168cacc5492f134ca7634bc1ebe --- .../systemui/unfold/UnfoldHapticsPlayer.kt | 30 +++- .../unfold/TestUnfoldTransitionProvider.kt | 4 + .../unfold/UnfoldHapticsPlayerTest.kt | 134 ++++++++++++++++++ 3 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt index 7726d09cf971e..8214822f03355 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt @@ -3,26 +3,43 @@ package com.android.systemui.unfold import android.os.SystemProperties import android.os.VibrationEffect import android.os.Vibrator +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.unfold.updates.FoldProvider.FoldCallback +import java.util.concurrent.Executor import javax.inject.Inject -/** - * Class that plays a haptics effect during unfolding a foldable device - */ +/** Class that plays a haptics effect during unfolding a foldable device */ @SysUIUnfoldScope class UnfoldHapticsPlayer @Inject constructor( unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + foldProvider: FoldProvider, + @Main private val mainExecutor: Executor, private val vibrator: Vibrator? ) : TransitionProgressListener { + private var isFirstAnimationAfterUnfold = false + init { if (vibrator != null) { // We don't need to remove the callback because we should listen to it // the whole time when SystemUI process is alive unfoldTransitionProgressProvider.addCallback(this) } + + foldProvider.registerCallback( + object : FoldCallback { + override fun onFoldUpdated(isFolded: Boolean) { + if (isFolded) { + isFirstAnimationAfterUnfold = true + } + } + }, + mainExecutor + ) } private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN @@ -36,6 +53,13 @@ constructor( } override fun onTransitionFinishing() { + // Run haptics only when unfolding the device (first animation after unfolding) + if (!isFirstAnimationAfterUnfold) { + return + } + + isFirstAnimationAfterUnfold = false + // Run haptics only if the animation is long enough to notice if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) { playHaptics() diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt index c31640279305f..4a28cd1de255e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt @@ -26,6 +26,10 @@ class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, Transitio listeners.forEach { it.onTransitionFinished() } } + override fun onTransitionFinishing() { + listeners.forEach { it.onTransitionFinishing() } + } + override fun onTransitionProgress(progress: Float) { listeners.forEach { it.onTransitionProgress(progress) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt new file mode 100644 index 0000000000000..d3fdbd94a5ac3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.unfold + +import android.os.VibrationEffect +import android.os.Vibrator +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class UnfoldHapticsPlayerTest : SysuiTestCase() { + + private val progressProvider = TestUnfoldTransitionProvider() + private val vibrator: Vibrator = mock() + private val testFoldProvider = TestFoldProvider() + + private lateinit var player: UnfoldHapticsPlayer + + @Before + fun before() { + player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, Runnable::run, vibrator) + } + + @Test + fun testUnfoldingTransitionFinishingEarly_playsHaptics() { + testFoldProvider.onFoldUpdate(isFolded = true) + testFoldProvider.onFoldUpdate(isFolded = false) + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.5f) + progressProvider.onTransitionFinishing() + + verify(vibrator).vibrate(any()) + } + + @Test + fun testUnfoldingTransitionFinishingLate_doesNotPlayHaptics() { + testFoldProvider.onFoldUpdate(isFolded = true) + testFoldProvider.onFoldUpdate(isFolded = false) + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.99f) + progressProvider.onTransitionFinishing() + + verify(vibrator, never()).vibrate(any()) + } + + @Test + fun testFoldingAfterUnfolding_doesNotPlayHaptics() { + // Unfold + testFoldProvider.onFoldUpdate(isFolded = true) + testFoldProvider.onFoldUpdate(isFolded = false) + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.5f) + progressProvider.onTransitionFinishing() + progressProvider.onTransitionFinished() + clearInvocations(vibrator) + + // Fold + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.5f) + progressProvider.onTransitionFinished() + testFoldProvider.onFoldUpdate(isFolded = true) + + verify(vibrator, never()).vibrate(any()) + } + + @Test + fun testUnfoldingAfterFoldingAndUnfolding_playsHaptics() { + // Unfold + testFoldProvider.onFoldUpdate(isFolded = true) + testFoldProvider.onFoldUpdate(isFolded = false) + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.5f) + progressProvider.onTransitionFinishing() + progressProvider.onTransitionFinished() + + // Fold + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.5f) + progressProvider.onTransitionFinished() + testFoldProvider.onFoldUpdate(isFolded = true) + clearInvocations(vibrator) + + // Unfold again + testFoldProvider.onFoldUpdate(isFolded = true) + testFoldProvider.onFoldUpdate(isFolded = false) + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.5f) + progressProvider.onTransitionFinishing() + progressProvider.onTransitionFinished() + + verify(vibrator).vibrate(any()) + } + + private class TestFoldProvider : FoldProvider { + private val listeners = arrayListOf() + + override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) { + listeners += callback + } + + override fun unregisterCallback(callback: FoldProvider.FoldCallback) { + listeners -= callback + } + + fun onFoldUpdate(isFolded: Boolean) { + listeners.forEach { it.onFoldUpdated(isFolded) } + } + } +}