diff options
| author | 2023-01-16 15:22:07 +0000 | |
|---|---|---|
| committer | 2023-01-17 12:33:12 +0000 | |
| commit | 8b28e859d66169a16acce89d1d7c79f1c18f7f86 (patch) | |
| tree | 172a7822fb83da1f9d672bdf3e2cadf51a415f30 | |
| parent | f1f50ee221a3cdb296493d652059a27a1683553d (diff) | |
[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
3 files changed, 165 insertions, 3 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt index 7726d09cf971..8214822f0335 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 c31640279305..4a28cd1de255 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 000000000000..d3fdbd94a5ac --- /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<VibrationEffect>()) + } + + @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<VibrationEffect>()) + } + + @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<VibrationEffect>()) + } + + @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<VibrationEffect>()) + } + + private class TestFoldProvider : FoldProvider { + private val listeners = arrayListOf<FoldProvider.FoldCallback>() + + 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) } + } + } +} |