summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt134
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) }
+ }
+ }
+}