diff options
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) + } +} |