diff --git a/CHANGELOG.md b/CHANGELOG.md index ada4f316fd2..0ac57060a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -584,6 +584,10 @@ This release depends on, and has been tested with, the following Mapbox dependen - Mapbox Core Common `v23.1.0-rc.2` - Mapbox Java `v6.8.0` ([release notes](https://github.com/mapbox/mapbox-java/releases/tag/v6.8.0)) - Mapbox Android Core `v5.0.2` ([release notes](https://github.com/mapbox/mapbox-events-android/releases/tag/core-5.0.2)) +- Add ability to check when `NavigationOptions` are changing with `MapboxNavigationApp.isOptionsChanging`. [#6484](https://github.com/mapbox/mapbox-navigation-android/pull/6484) +- Add ability to check when Lifecycle events are triggered by configuration changes with `MapboxNavigationApp.isConfigurationChanging`. [#6484](https://github.com/mapbox/mapbox-navigation-android/pull/6484) +- Added ability to check when `NavigationOptions` are changing with `MapboxNavigationApp.isOptionsChanging`. [#6484](https://github.com/mapbox/mapbox-navigation-android/pull/6484) +- Added ability to check when Lifecycle events are triggered by configuration changes with `MapboxNavigationApp.isConfigurationChanging`. [#6484](https://github.com/mapbox/mapbox-navigation-android/pull/6484) ## Mapbox Navigation SDK 2.9.0-beta.2 - 14 October, 2022 ### Changelog diff --git a/libnavigation-core/build.gradle b/libnavigation-core/build.gradle index 33033100ed4..e4c902924d2 100644 --- a/libnavigation-core/build.gradle +++ b/libnavigation-core/build.gradle @@ -61,6 +61,7 @@ dependencies { testImplementation project(':libtesting-utils') testImplementation project(':libtesting-navigation-base') testImplementation project(':libtesting-navigation-util') + testImplementation(dependenciesList.androidXLifecycleTesting) apply from: "${rootDir}/gradle/unit-testing-dependencies.gradle" testImplementation dependenciesList.commonsIO diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationApp.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationApp.kt index f7d722d02eb..de1016edc21 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationApp.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationApp.kt @@ -77,6 +77,25 @@ object MapboxNavigationApp { @JvmStatic fun isSetup(): Boolean = mapboxNavigationAppDelegate.isSetup + /** + * When calling [setup] multiple times, all registered [MapboxNavigationObserver] will be + * detached from the instance of [MapboxNavigation] that is being destroyed. This is needed for + * maintaining state across options changes. You do not need to clear the state when + * [isOptionsChanging] is true. + */ + @UiThread + @JvmStatic + fun isOptionsChanging(): Boolean = mapboxNavigationAppDelegate.isOptionsChanging + + /** + * When observing [MapboxNavigation] from a [Lifecycle], the [Lifecycle.Event] may be triggered + * because of a configuration change. This will help you know if the lifecycle events are + * triggered events like mobile device orientation changes. + */ + @UiThread + @JvmStatic + fun isConfigurationChanging(): Boolean = mapboxNavigationAppDelegate.isConfigurationChanging() + /** * Call [MapboxNavigationApp.setup] to provide the application with [NavigationOptions]. * diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegate.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegate.kt index 195a6b3c44e..37f32e0fa56 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegate.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegate.kt @@ -16,7 +16,11 @@ internal class MapboxNavigationAppDelegate { val lifecycleOwner: LifecycleOwner by lazy { carAppLifecycleOwner } + var isOptionsChanging = false + private set + var isSetup = false + private set fun setup(navigationOptionsProvider: NavigationOptionsProvider) = apply { if (carAppLifecycleOwner.isConfigurationChanging()) { @@ -24,14 +28,19 @@ internal class MapboxNavigationAppDelegate { } if (isSetup) { + isOptionsChanging = true disable() } - mapboxNavigationOwner.setup(navigationOptionsProvider) carAppLifecycleOwner.lifecycle.addObserver(mapboxNavigationOwner.carAppLifecycleObserver) + isOptionsChanging = false isSetup = true } + fun isConfigurationChanging(): Boolean { + return carAppLifecycleOwner.isConfigurationChanging() + } + fun attachAllActivities(application: Application) { carAppLifecycleOwner.attachAllActivities(application) } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/CarAppLifecycleOwnerTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/CarAppLifecycleOwnerTest.kt index e24f159e587..dd4a4ff6578 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/CarAppLifecycleOwnerTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/CarAppLifecycleOwnerTest.kt @@ -8,8 +8,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.testing.TestLifecycleOwner import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.testing.LoggingFrontendTestRule import io.mockk.every @@ -405,8 +404,8 @@ class CarAppLifecycleOwnerTest { val testLifecycleOwner = TestLifecycleOwner() carAppLifecycleOwner.attach(testLifecycleOwner) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.DESTROYED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) verify(exactly = 1) { testLifecycleObserver.onCreate(any()) } verify(exactly = 1) { testLifecycleObserver.onStart(any()) } @@ -422,10 +421,10 @@ class CarAppLifecycleOwnerTest { carAppLifecycleOwner.attach(testLifecycleOwnerA) carAppLifecycleOwner.attach(testLifecycleOwnerB) - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.DESTROYED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.DESTROYED + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) verify(exactly = 1) { testLifecycleObserver.onCreate(any()) } verify(exactly = 1) { testLifecycleObserver.onStart(any()) } @@ -441,10 +440,10 @@ class CarAppLifecycleOwnerTest { carAppLifecycleOwner.attach(testLifecycleOwnerA) carAppLifecycleOwner.attach(testLifecycleOwnerB) - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.STARTED - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.DESTROYED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.DESTROYED + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) verify(exactly = 1) { testLifecycleObserver.onCreate(any()) } verify(exactly = 1) { testLifecycleObserver.onStart(any()) } @@ -458,7 +457,7 @@ class CarAppLifecycleOwnerTest { val testLifecycleOwner = TestLifecycleOwner() carAppLifecycleOwner.attach(testLifecycleOwner) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) carAppLifecycleOwner.detach(testLifecycleOwner) verify(exactly = 1) { testLifecycleObserver.onCreate(any()) } @@ -468,13 +467,6 @@ class CarAppLifecycleOwnerTest { verify(exactly = 0) { testLifecycleObserver.onDestroy(any()) } } - class TestLifecycleOwner : LifecycleOwner { - val lifecycleRegistry = LifecycleRegistry(this) - .also { it.currentState = Lifecycle.State.INITIALIZED } - - override fun getLifecycle(): Lifecycle = lifecycleRegistry - } - private fun mockActivity(isChangingConfig: Boolean = false): FragmentActivity = mockk { every { isChangingConfigurations } returns isChangingConfig } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegateTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegateTest.kt index 01ff5d19529..7a9538be2e2 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegateTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/lifecycle/MapboxNavigationAppDelegateTest.kt @@ -6,6 +6,7 @@ import androidx.core.app.ComponentActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.testing.TestLifecycleOwner import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.core.MapboxNavigation @@ -20,6 +21,7 @@ import io.mockk.unmockkAll import io.mockk.verify import io.mockk.verifyOrder import org.junit.After +import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -59,13 +61,13 @@ class MapboxNavigationAppDelegateTest { fun `verify onAttached and onDetached when multiple lifecycles have started`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwnerA = CarAppLifecycleOwnerTest.TestLifecycleOwner() - val testLifecycleOwnerB = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwnerA = TestLifecycleOwner(initialState = Lifecycle.State.CREATED) + val testLifecycleOwnerB = TestLifecycleOwner(initialState = Lifecycle.State.CREATED) mapboxNavigationApp.attach(testLifecycleOwnerA) mapboxNavigationApp.attach(testLifecycleOwnerB) - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) val observer = mockk(relaxUnitFun = true) mapboxNavigationApp.registerObserver(observer) @@ -86,10 +88,10 @@ class MapboxNavigationAppDelegateTest { mapboxNavigationApp.registerObserver(firstObserver) mapboxNavigationApp.registerObserver(secondObserver) - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) verify(exactly = 1) { firstObserver.onAttached(any()) } verify(exactly = 1) { secondObserver.onAttached(any()) } @@ -104,10 +106,10 @@ class MapboxNavigationAppDelegateTest { mapboxNavigationApp.registerObserver(firstObserver) mapboxNavigationApp.registerObserver(secondObserver) - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) mapboxNavigationApp.setup { navigationOptions } verify(exactly = 1) { firstObserver.onAttached(any()) } @@ -126,10 +128,10 @@ class MapboxNavigationAppDelegateTest { } mapboxNavigationApp.registerObserver(observer) - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) mapboxNavigationApp.setup { navigationOptions } - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) mapboxNavigationApp.setup { navigationOptions } assertEquals(2, attachedSlot.size) @@ -217,13 +219,13 @@ class MapboxNavigationAppDelegateTest { fun `verify detaching all LifecycleOwners detaches all observers`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwnerA = CarAppLifecycleOwnerTest.TestLifecycleOwner() - val testLifecycleOwnerB = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwnerA = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) + val testLifecycleOwnerB = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwnerA) mapboxNavigationApp.attach(testLifecycleOwnerB) - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) val firstObserver = mockk(relaxUnitFun = true) val secondObserver = mockk(relaxUnitFun = true) @@ -245,13 +247,13 @@ class MapboxNavigationAppDelegateTest { fun `verify disable will call observers onDetached`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwnerA = CarAppLifecycleOwnerTest.TestLifecycleOwner() - val testLifecycleOwnerB = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwnerA = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) + val testLifecycleOwnerB = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwnerA) mapboxNavigationApp.attach(testLifecycleOwnerB) - testLifecycleOwnerA.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwnerB.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwnerA.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwnerB.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) val firstObserver = mockk(relaxUnitFun = true) val secondObserver = mockk(relaxUnitFun = true) @@ -272,9 +274,9 @@ class MapboxNavigationAppDelegateTest { fun `verify disable will prevent mapboxNavigation from restarting`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) val observer = mockk(relaxUnitFun = true) mapboxNavigationApp.registerObserver(observer) @@ -291,9 +293,9 @@ class MapboxNavigationAppDelegateTest { fun `verify disable will detach and current becomes null`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) val observer = mockk(relaxUnitFun = true) mapboxNavigationApp.registerObserver(observer) @@ -308,13 +310,13 @@ class MapboxNavigationAppDelegateTest { fun `verify current is null when all lifecycle owners are destroyed`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) val observer = mockk(relaxUnitFun = true) mapboxNavigationApp.registerObserver(observer) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.DESTROYED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) assertNull(mapboxNavigationApp.current()) } @@ -323,21 +325,21 @@ class MapboxNavigationAppDelegateTest { fun `verify current is set after LifecycleOwner is created`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) mapboxNavigationApp.attach(testLifecycleOwner) val observer = mockk(relaxUnitFun = true) mapboxNavigationApp.registerObserver(observer) assertNull(mapboxNavigationApp.current()) - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.CREATED + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) assertNotNull(mapboxNavigationApp.current()) } @Test fun `verify MapboxNavigationObserver lifecycles are called once`() { mapboxNavigationApp.setup { navigationOptions } - val testLifecycleOwner = CarAppLifecycleOwnerTest.TestLifecycleOwner() - testLifecycleOwner.lifecycleRegistry.currentState = Lifecycle.State.RESUMED + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) mapboxNavigationApp.attach(testLifecycleOwner) val observer = mockk(relaxUnitFun = true) @@ -476,6 +478,62 @@ class MapboxNavigationAppDelegateTest { assertEquals(retrieved, retrievedJava) } + @Test + fun `verify isConfigurationChanging is false for setup changes`() { + mockMultiMapboxNavigation() + mapboxNavigationApp.setup { navigationOptions } + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + mapboxNavigationApp.attach(testLifecycleOwner) + + val onAttachedSlot = mutableListOf() + val onDetachedSlot = mutableListOf() + val observerFirst = object : MapboxNavigationObserver { + override fun onAttached(mapboxNavigation: MapboxNavigation) { + onAttachedSlot.add(mapboxNavigationApp.isConfigurationChanging()) + } + + override fun onDetached(mapboxNavigation: MapboxNavigation) { + onDetachedSlot.add(mapboxNavigationApp.isConfigurationChanging()) + } + } + mapboxNavigationApp.registerObserver(observerFirst) + mapboxNavigationApp.setup { mockk() } + mapboxNavigationApp.setup { mockk() } + mapboxNavigationApp.disable() + + assertArrayEquals(booleanArrayOf(false, false, false), onAttachedSlot.toBooleanArray()) + assertArrayEquals(booleanArrayOf(false, false, false), onDetachedSlot.toBooleanArray()) + } + + @Test + fun `verify isOptionsChanging is true while the setup changes`() { + mockMultiMapboxNavigation() + mapboxNavigationApp.setup { navigationOptions } + val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.INITIALIZED) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + mapboxNavigationApp.attach(testLifecycleOwner) + + val onAttachedSlot = mutableListOf() + val onDetachedSlot = mutableListOf() + val observerFirst = object : MapboxNavigationObserver { + override fun onAttached(mapboxNavigation: MapboxNavigation) { + onAttachedSlot.add(mapboxNavigationApp.isOptionsChanging()) + } + + override fun onDetached(mapboxNavigation: MapboxNavigation) { + onDetachedSlot.add(mapboxNavigationApp.isOptionsChanging()) + } + } + mapboxNavigationApp.registerObserver(observerFirst) + mapboxNavigationApp.setup { mockk() } + mapboxNavigationApp.setup { mockk() } + mapboxNavigationApp.disable() + + assertArrayEquals(booleanArrayOf(false, true, true), onAttachedSlot.toBooleanArray()) + assertArrayEquals(booleanArrayOf(true, true, false), onDetachedSlot.toBooleanArray()) + } + private fun mockActivityLifecycle(): Pair { val activity = mockk { every { isChangingConfigurations } returns false @@ -486,6 +544,14 @@ class MapboxNavigationAppDelegateTest { return Pair(activity, lifecycle) } + private fun mockMultiMapboxNavigation() { + every { MapboxNavigationProvider.create(any()) } answers { + mockk { + every { navigationOptions } returns firstArg() + } + } + } + /** * Used for the [MapboxNavigationApp.getObserver] tests because they require a class definition. */