summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nicolo' Mazzucato <nicomazz@google.com> 2022-01-06 14:49:44 +0100
committer Nicolo' Mazzucato <nicomazz@google.com> 2022-01-07 14:47:37 +0100
commit2e4d663ee05b16c2767733b39ee1f153bcaca1e4 (patch)
tree128aee0a2267c799474cc9beeb46a61adb2c1750
parent5223943b0e6766529085cba0a55f8b3d26740b62 (diff)
Create FoldStateLoggingProvider
The aim of this class it to output fold state changes with the time between them. Those will be logged in a follow up change to uptimize fold/unfold experience. + Changed files have been reformatted according to ktfmt. Those are minimal changes. + Making some return type explicit in dagger modules Bug: 198305865 Test: atest FoldStateLoggingProviderTest Change-Id: I3dcfa742f2cf3c712796b5c6f5481f319b680a12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt192
6 files changed, 452 insertions, 49 deletions
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 24e93efa09cb..953b0e018306 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -21,24 +21,24 @@ import android.content.Context
import android.hardware.SensorManager
import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
-import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
-import java.lang.IllegalStateException
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
import java.util.concurrent.Executor
/**
* Factory for [UnfoldTransitionProgressProvider].
*
- * This is needed as Launcher has to create the object manually.
- * Sysui create it using dagger (see [UnfoldTransitionModule]).
+ * This is needed as Launcher has to create the object manually. Sysui create it using dagger (see
+ * [UnfoldTransitionModule]).
*/
fun createUnfoldTransitionProgressProvider(
context: Context,
@@ -52,10 +52,45 @@ fun createUnfoldTransitionProgressProvider(
): UnfoldTransitionProgressProvider {
if (!config.isEnabled) {
- throw IllegalStateException("Trying to create " +
- "UnfoldTransitionProgressProvider when the transition is disabled")
+ throw IllegalStateException(
+ "Trying to create " +
+ "UnfoldTransitionProgressProvider when the transition is disabled")
}
+ val foldStateProvider =
+ createFoldStateProvider(
+ context,
+ config,
+ screenStatusProvider,
+ deviceStateManager,
+ sensorManager,
+ mainHandler,
+ mainExecutor)
+
+ val unfoldTransitionProgressProvider =
+ if (config.isHingeAngleEnabled) {
+ PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
+ } else {
+ FixedTimingTransitionProgressProvider(foldStateProvider)
+ }
+
+ return ScaleAwareTransitionProgressProvider(
+ unfoldTransitionProgressProvider, context.contentResolver)
+ .apply {
+ // Always present callback that logs animation beginning and end.
+ addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix))
+ }
+}
+
+fun createFoldStateProvider(
+ context: Context,
+ config: UnfoldTransitionConfig,
+ screenStatusProvider: ScreenStatusProvider,
+ deviceStateManager: DeviceStateManager,
+ sensorManager: SensorManager,
+ mainHandler: Handler,
+ mainExecutor: Executor
+): FoldStateProvider {
val hingeAngleProvider =
if (config.isHingeAngleEnabled) {
HingeSensorAngleProvider(sensorManager)
@@ -63,28 +98,13 @@ fun createUnfoldTransitionProgressProvider(
EmptyHingeAngleProvider()
}
- val foldStateProvider = DeviceFoldStateProvider(
+ return DeviceFoldStateProvider(
context,
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
mainExecutor,
- mainHandler
- )
-
- val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
- } else {
- FixedTimingTransitionProgressProvider(foldStateProvider)
- }
- return ScaleAwareTransitionProgressProvider(
- unfoldTransitionProgressProvider,
- context.contentResolver
- ).apply {
- // Always present callback that logs animation beginning and end.
- addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix))
- }
+ mainHandler)
}
-fun createConfig(context: Context): UnfoldTransitionConfig =
- ResourceUnfoldTransitionConfig(context)
+fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt
new file mode 100644
index 000000000000..1f5959e879bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.annotation.IntDef
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener
+import com.android.systemui.unfold.FoldStateLoggingProvider.LoggedFoldedStates
+
+/** Reports device fold states for logging purposes. */
+// TODO(b/198305865): Log state changes.
+interface FoldStateLoggingProvider : CallbackController<FoldStateLoggingListener> {
+
+ fun init()
+ fun uninit()
+
+ interface FoldStateLoggingListener {
+ fun onFoldUpdate(foldStateUpdate: FoldStateChange)
+ }
+
+ @IntDef(prefix = ["LOGGED_FOLD_STATE_"], value = [FULLY_OPENED, FULLY_CLOSED, HALF_OPENED])
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class LoggedFoldedStates
+}
+
+data class FoldStateChange(
+ @LoggedFoldedStates val previous: Int,
+ @LoggedFoldedStates val current: Int,
+ val dtMillis: Long
+)
+
+const val FULLY_OPENED = 1
+const val FULLY_CLOSED = 2
+const val HALF_OPENED = 3
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt
new file mode 100644
index 000000000000..2683971f852c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 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.util.Log
+import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener
+import com.android.systemui.unfold.FoldStateLoggingProvider.LoggedFoldedStates
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import com.android.systemui.util.time.SystemClock
+
+/**
+ * Reports device fold states for logging purposes.
+ *
+ * Wraps the state provided by [FoldStateProvider] to output only [FULLY_OPENED], [FULLY_CLOSED] and
+ * [HALF_OPENED] for logging purposes.
+ *
+ * Note that [HALF_OPENED] state is only emitted after the device angle is stable for some timeout.
+ * Check [FoldStateProvider] impl for it.
+ *
+ * This doesn't log the following transitions:
+ * - [HALF_OPENED] -> [FULLY_OPENED]: not interesting, as there is no transition going on
+ * - [HALF_OPENED] -> [HALF_OPENED]: not meaningful.
+ */
+class FoldStateLoggingProviderImpl(
+ private val foldStateProvider: FoldStateProvider,
+ private val clock: SystemClock
+) : FoldStateLoggingProvider, FoldStateProvider.FoldUpdatesListener {
+
+ private val outputListeners: MutableList<FoldStateLoggingListener> = mutableListOf()
+
+ @LoggedFoldedStates private var lastState: Int? = null
+ private var actionStartMillis: Long? = null
+
+ override fun init() {
+ foldStateProvider.addCallback(this)
+ foldStateProvider.start()
+ }
+
+ override fun uninit() {
+ foldStateProvider.removeCallback(this)
+ foldStateProvider.stop()
+ }
+
+ override fun onHingeAngleUpdate(angle: Float) {}
+
+ override fun onFoldUpdate(@FoldUpdate update: Int) {
+ val now = clock.elapsedRealtime()
+ when (update) {
+ FOLD_UPDATE_START_OPENING -> {
+ lastState = FULLY_CLOSED
+ actionStartMillis = now
+ }
+ FOLD_UPDATE_START_CLOSING -> actionStartMillis = now
+ FOLD_UPDATE_FINISH_HALF_OPEN -> dispatchState(HALF_OPENED)
+ FOLD_UPDATE_FINISH_FULL_OPEN -> dispatchState(FULLY_OPENED)
+ FOLD_UPDATE_FINISH_CLOSED -> dispatchState(FULLY_CLOSED)
+ }
+ }
+
+ private fun dispatchState(@LoggedFoldedStates current: Int) {
+ val now = clock.elapsedRealtime()
+ val previous = lastState
+ val lastActionStart = actionStartMillis
+
+ if (previous != null && previous != current && lastActionStart != null) {
+ val time = now - lastActionStart
+ val foldStateChange = FoldStateChange(previous, current, time)
+ outputListeners.forEach { it.onFoldUpdate(foldStateChange) }
+ if (DEBUG) {
+ Log.d(TAG, "From $previous to $current in $time")
+ }
+ }
+
+ actionStartMillis = null
+ lastState = current
+ }
+
+ override fun addCallback(listener: FoldStateLoggingListener) {
+ outputListeners.add(listener)
+ }
+
+ override fun removeCallback(listener: FoldStateLoggingListener) {
+ outputListeners.remove(listener)
+ }
+}
+
+private const val DEBUG = false
+private const val TAG = "FoldStateLoggingProviderImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index ccde3162b177..07f9c5487c41 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -17,10 +17,11 @@
package com.android.systemui.unfold
import com.android.keyguard.KeyguardUnfoldTransition
-import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
-import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.kotlin.getOrNull
import dagger.BindsInstance
import dagger.Module
import dagger.Provides
@@ -36,15 +37,17 @@ annotation class SysUIUnfoldScope
/**
* Creates an injectable [SysUIUnfoldComponent] that provides objects that have been scoped with
- * [@SysUIUnfoldScope]. Since [SysUIUnfoldComponent] depends upon:
+ * [@SysUIUnfoldScope].
+ *
+ * Since [SysUIUnfoldComponent] depends upon:
* * [Optional<UnfoldTransitionProgressProvider>]
* * [Optional<ScopedUnfoldTransitionProgressProvider>]
* * [Optional<NaturalRotationProgressProvider>]
+ *
* no objects will get constructed if these parameters are empty.
*/
@Module(subcomponents = [SysUIUnfoldComponent::class])
class SysUIUnfoldModule {
- constructor() {}
@Provides
@SysUISingleton
@@ -53,12 +56,16 @@ class SysUIUnfoldModule {
rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>,
@Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
factory: SysUIUnfoldComponent.Factory
- ) =
- provider.flatMap { p1 ->
- rotationProvider.flatMap { p2 ->
- scopedProvider.map { p3 -> factory.create(p1, p2, p3) }
- }
+ ): Optional<SysUIUnfoldComponent> {
+ val p1 = provider.getOrNull()
+ val p2 = rotationProvider.getOrNull()
+ val p3 = scopedProvider.getOrNull()
+ return if (p1 == null || p2 == null || p3 == null) {
+ Optional.empty()
+ } else {
+ Optional.of(factory.create(p1, p2, p3))
}
+ }
}
@SysUIUnfoldScope
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 75dfd48ad9f3..f2c156108ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -24,8 +24,10 @@ import android.view.IWindowManager
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.time.SystemClockImpl
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
import dagger.Lazy
import dagger.Module
@@ -48,7 +50,7 @@ class UnfoldTransitionModule {
sensorManager: SensorManager,
@Main executor: Executor,
@Main handler: Handler
- ) =
+ ): Optional<UnfoldTransitionProgressProvider> =
if (config.isEnabled) {
Optional.of(
createUnfoldTransitionProgressProvider(
@@ -59,15 +61,47 @@ class UnfoldTransitionModule {
sensorManager,
handler,
executor,
- tracingTagPrefix = "systemui"
- )
- )
+ tracingTagPrefix = "systemui"))
} else {
Optional.empty()
}
@Provides
@Singleton
+ fun provideFoldStateProvider(
+ context: Context,
+ config: UnfoldTransitionConfig,
+ screenStatusProvider: Lazy<LifecycleScreenStatusProvider>,
+ deviceStateManager: DeviceStateManager,
+ sensorManager: SensorManager,
+ @Main executor: Executor,
+ @Main handler: Handler
+ ): Optional<FoldStateProvider> =
+ if (!config.isHingeAngleEnabled) {
+ Optional.empty()
+ } else {
+ Optional.of(
+ createFoldStateProvider(
+ context,
+ config,
+ screenStatusProvider.get(),
+ deviceStateManager,
+ sensorManager,
+ handler,
+ executor))
+ }
+
+ @Provides
+ @Singleton
+ fun providesFoldStateLoggingProvider(
+ optionalFoldStateProvider: Optional<FoldStateProvider>
+ ): Optional<FoldStateLoggingProvider> =
+ optionalFoldStateProvider.map { foldStateProvider ->
+ FoldStateLoggingProviderImpl(foldStateProvider, SystemClockImpl())
+ }
+
+ @Provides
+ @Singleton
fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
createConfig(context)
@@ -77,13 +111,9 @@ class UnfoldTransitionModule {
context: Context,
windowManager: IWindowManager,
unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider>
- ) =
- unfoldTransitionProgressProvider.map {
- provider -> NaturalRotationUnfoldProgressProvider(
- context,
- windowManager,
- provider
- )
+ ): Optional<NaturalRotationUnfoldProgressProvider> =
+ unfoldTransitionProgressProvider.map { provider ->
+ NaturalRotationUnfoldProgressProvider(context, windowManager, provider)
}
@Provides
@@ -91,10 +121,8 @@ class UnfoldTransitionModule {
@Singleton
fun provideStatusBarScopedTransitionProvider(
source: Optional<NaturalRotationUnfoldProgressProvider>
- ) =
- source.map {
- provider -> ScopedUnfoldTransitionProgressProvider(provider)
- }
+ ): Optional<ScopedUnfoldTransitionProgressProvider> =
+ source.map { provider -> ScopedUnfoldTransitionProgressProvider(provider) }
@Provides
@Singleton
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
new file mode 100644
index 000000000000..8076b4eda883
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 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.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FoldStateLoggingProviderTest : SysuiTestCase() {
+
+ @Captor
+ private lateinit var foldUpdatesListener: ArgumentCaptor<FoldStateProvider.FoldUpdatesListener>
+
+ @Mock private lateinit var foldStateProvider: FoldStateProvider
+
+ private val fakeClock = FakeSystemClock()
+
+ private lateinit var foldStateLoggingProvider: FoldStateLoggingProvider
+
+ private val foldLoggingUpdates: MutableList<FoldStateChange> = arrayListOf()
+
+ private val foldStateLoggingListener =
+ object : FoldStateLoggingListener {
+ override fun onFoldUpdate(foldStateUpdate: FoldStateChange) {
+ foldLoggingUpdates.add(foldStateUpdate)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ foldStateLoggingProvider =
+ FoldStateLoggingProviderImpl(foldStateProvider, fakeClock).apply {
+ addCallback(foldStateLoggingListener)
+ init()
+ }
+
+ verify(foldStateProvider).addCallback(foldUpdatesListener.capture())
+ }
+
+ @Test
+ fun onFoldUpdate_noPreviousOne_finishHalfOpen_nothingReported() {
+ sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+
+ assertThat(foldLoggingUpdates).isEmpty()
+ }
+
+ @Test
+ fun onFoldUpdate_noPreviousOne_finishFullOpen_nothingReported() {
+ sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+
+ assertThat(foldLoggingUpdates).isEmpty()
+ }
+
+ @Test
+ fun onFoldUpdate_noPreviousOne_finishClosed_nothingReported() {
+ sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+
+ assertThat(foldLoggingUpdates).isEmpty()
+ }
+
+ @Test
+ fun onFoldUpdate_startOpening_fullOpen_changeReported() {
+ val dtTime = 10L
+
+ sendFoldUpdate(FOLD_UPDATE_START_OPENING)
+ fakeClock.advanceTime(dtTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+
+ assertThat(foldLoggingUpdates)
+ .containsExactly(FoldStateChange(FULLY_CLOSED, FULLY_OPENED, dtTime))
+ }
+
+ @Test
+ fun onFoldUpdate_startClosingThenFinishClosed_noInitialState_nothingReported() {
+ val dtTime = 10L
+
+ sendFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ fakeClock.advanceTime(dtTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+
+ assertThat(foldLoggingUpdates).isEmpty()
+ }
+
+ @Test
+ fun onFoldUpdate_startClosingThenFinishClosed_initiallyOpened_changeReported() {
+ val dtTime = 10L
+ sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+
+ sendFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ fakeClock.advanceTime(dtTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+
+ assertThat(foldLoggingUpdates)
+ .containsExactly(FoldStateChange(FULLY_OPENED, FULLY_CLOSED, dtTime))
+ }
+
+ @Test
+ fun onFoldUpdate_startOpeningThenHalf_initiallyClosed_changeReported() {
+ val dtTime = 10L
+ sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+
+ sendFoldUpdate(FOLD_UPDATE_START_OPENING)
+ fakeClock.advanceTime(dtTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+
+ assertThat(foldLoggingUpdates)
+ .containsExactly(FoldStateChange(FULLY_CLOSED, HALF_OPENED, dtTime))
+ }
+
+ @Test
+ fun onFoldUpdate_startClosingThenHalf_initiallyOpened_changeReported() {
+ val dtTime = 10L
+ sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+
+ sendFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ fakeClock.advanceTime(dtTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+
+ assertThat(foldLoggingUpdates)
+ .containsExactly(FoldStateChange(FULLY_OPENED, HALF_OPENED, dtTime))
+ }
+
+ @Test
+ fun onFoldUpdate_foldThenUnfold_multipleReported() {
+ val foldTime = 24L
+ val unfoldTime = 42L
+ val waitingTime = 424L
+ sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+
+ // Fold
+ sendFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ fakeClock.advanceTime(foldTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ fakeClock.advanceTime(waitingTime)
+ // unfold
+ sendFoldUpdate(FOLD_UPDATE_START_OPENING)
+ fakeClock.advanceTime(unfoldTime)
+ sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+
+ assertThat(foldLoggingUpdates)
+ .containsExactly(
+ FoldStateChange(FULLY_OPENED, FULLY_CLOSED, foldTime),
+ FoldStateChange(FULLY_CLOSED, FULLY_OPENED, unfoldTime))
+ }
+
+ @Test
+ fun uninit_removesCallback() {
+ foldStateLoggingProvider.uninit()
+
+ verify(foldStateProvider).removeCallback(foldUpdatesListener.value)
+ }
+
+ private fun sendFoldUpdate(@FoldUpdate foldUpdate: Int) {
+ foldUpdatesListener.value.onFoldUpdate(foldUpdate)
+ }
+}