diff --git a/instrumentation-tests/build.gradle b/instrumentation-tests/build.gradle index 4a8022a5a90..d8159a0c699 100644 --- a/instrumentation-tests/build.gradle +++ b/instrumentation-tests/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation project(':libnavigation-android') implementation project(':libnavui-dropin') implementation project(":libnavui-util") + implementation project(':libnavigator') // test androidTestImplementation project(':libtesting-ui') diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/TelemetryEventsTest.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/TelemetryEventsTest.kt new file mode 100644 index 00000000000..bd93dd47ec3 --- /dev/null +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/TelemetryEventsTest.kt @@ -0,0 +1,428 @@ +package com.mapbox.navigation.instrumentation_tests.core + +import android.content.Context +import android.location.Location +import androidx.test.platform.app.InstrumentationRegistry +import com.mapbox.api.directions.v5.DirectionsCriteria +import com.mapbox.api.directions.v5.models.DirectionsResponse +import com.mapbox.bindgen.Value +import com.mapbox.common.SettingsServiceFactory +import com.mapbox.common.SettingsServiceStorageType +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.base.options.DeviceProfile +import com.mapbox.navigation.base.options.NavigationOptions +import com.mapbox.navigation.base.trip.model.RouteProgressState +import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigation.core.MapboxNavigationProvider +import com.mapbox.navigation.core.telemetry.events.UserFeedback +import com.mapbox.navigation.instrumentation_tests.R +import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity +import com.mapbox.navigation.instrumentation_tests.utils.events.EventsAccumulatorRule +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventAlternativeRoute +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventArrive +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventBase +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventCancel +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventDepart +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventFeedback +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventFreeDrive +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventNavigationStateChanged +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventReroute +import com.mapbox.navigation.instrumentation_tests.utils.events.verify.verifyEvents +import com.mapbox.navigation.instrumentation_tests.utils.http.EventsRequestHandle +import com.mapbox.navigation.instrumentation_tests.utils.http.MockDirectionsRequestHandler +import com.mapbox.navigation.instrumentation_tests.utils.location.MockLocationReplayerRule +import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText +import com.mapbox.navigation.instrumentation_tests.utils.routes.MockRoute +import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider +import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider.toNavigationRoutes +import com.mapbox.navigation.testing.ui.BaseTest +import com.mapbox.navigation.testing.ui.utils.MapboxNavigationRule +import com.mapbox.navigation.testing.ui.utils.coroutines.passed +import com.mapbox.navigation.testing.ui.utils.coroutines.rawLocationUpdates +import com.mapbox.navigation.testing.ui.utils.coroutines.routeProgressUpdates +import com.mapbox.navigation.testing.ui.utils.coroutines.sdkTest +import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources +import com.mapbox.navigation.testing.ui.utils.runOnMainSync +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.sample +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) +class TelemetryEventsTest : BaseTest(EmptyTestActivity::class.java) { + + @get:Rule + val eventsAccumulatorRule = EventsAccumulatorRule( + getMapboxAccessTokenFromResources(InstrumentationRegistry.getInstrumentation().targetContext) + ) + + @get:Rule + val mockLocationReplayerRule = MockLocationReplayerRule(mockLocationUpdatesRule) + + @get:Rule + val mapboxNavigationRule = MapboxNavigationRule() + + private val coordinates = listOf( + Point.fromLngLat(-77.031991, 38.894721), + Point.fromLngLat(-77.031991, 38.895433), + Point.fromLngLat(-77.030923, 38.895433), + ) + + // private lateinit var mapboxNavigation: MapboxNavigation + private val eventsRequestHandle = EventsRequestHandle() + + private companion object { + private const val LOG_CATEGORY = "TelemetryEventsTest" + + /* See mapbox_common_settings_internal.hpp in common repo */ + private const val TELEMETRY_EVENTS_BASE_URL_PROPERTY = + "com.mapbox.common.telemetry.internal.events_base_url_override" + + private fun createMapboxNavigation(context: Context): MapboxNavigation = + MapboxNavigationProvider.create( + NavigationOptions.Builder(context) + .accessToken(getMapboxAccessTokenFromResources(context)) + .deviceProfile( + DeviceProfile.Builder() + .customConfig( + """ + { + "features": { + "useTelemetryNavigationEvents": true + }, + "telemetry": { + "eventsPriority": "Immediate" + } + } + """.trimIndent() + ) + .build() + ) + .build() + ) + + /** + * Give the chance to send events: 5 attempts with 1 seconds delay + */ + private suspend fun waitForEventsBeSent(expectEvents: Int, current: () -> Int) { + IntRange(0, 4).forEach { _ -> + if (expectEvents == current()) { + return + } else { + delay(1_000) + } + } + } + + private fun List.prettyErrors(): String = joinToString(separator = "") { "\n$it" } + } + + override fun setupMockLocation(): Location = mockLocationUpdatesRule.generateLocationUpdate { + latitude = coordinates[0].latitude() + longitude = coordinates[0].longitude() + } + + @Before + fun setup() { + runOnMainSync { + // substitute telemetry base url + SettingsServiceFactory.getInstance(SettingsServiceStorageType.NON_PERSISTENT).apply { + set(TELEMETRY_EVENTS_BASE_URL_PROPERTY, Value(mockWebServerRule.baseUrl)) + } + + mockWebServerRule.requestHandlers.add(eventsRequestHandle) + } + } + + @Test + fun freeDrivePlain() = sdkTest { + val dcShorRoute = RoutesProvider.dc_short_with_alternative(activity).apply { + setRouteAsOriginLocation() + } + val mapboxNavigation = createMapboxNavigation(activity) + + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShorRoute.routeResponse.routes().first()) + mapboxNavigation.rawLocationUpdates().passed(50.0).first() + mapboxNavigation.stopTripSession() + + verifyResult( + EventFreeDrive(EventFreeDrive.Type.Start), + EventFreeDrive(EventFreeDrive.Type.Stop), + ) + } + + @Test + fun activeGuidancePlain() = sdkTest { + val dcShortRoute = RoutesProvider.dc_short_with_alternative(activity).apply { + setRouteAsOriginLocation() + } + val mapboxNavigation = createMapboxNavigation(activity) + mockLocationReplayerRule.playbackSpeed(3.0) + + mapboxNavigation.setNavigationRoutes(dcShortRoute.toNavigationRoutes()) + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShortRoute.routeResponse.routes().first()) + mapboxNavigation.routeProgressUpdates().first { + it.currentState == RouteProgressState.COMPLETE + } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventDepart(), + EventArrive(), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + @Test + fun freeDriveWithFeedback() = sdkTest { + val dcShorRoute = RoutesProvider.dc_very_short_two_legs(activity) + val mapboxNavigation = createMapboxNavigation(activity) + val feedbackType = "feedbackType" + val description = "description" + val feedbackSubTypes = arrayOf("subType1", "subType2") + + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShorRoute.routeResponse.routes().first()) + mapboxNavigation.rawLocationUpdates().passed(20.0).first() + mapboxNavigation.postUserFeedback( + UserFeedback.Builder(feedbackType, description) + .feedbackSubTypes(feedbackSubTypes) + .build(), + ) + mapboxNavigation.rawLocationUpdates().passed(20.0).first() + mapboxNavigation.stopTripSession() + + verifyResult( + EventFreeDrive(EventFreeDrive.Type.Start), + EventFeedback( + driverMode = EventBase.DriverMode.FreeDrive, + feedbackType = feedbackType, + description = description, + feedbackSubType = feedbackSubTypes, + ), + EventFreeDrive(EventFreeDrive.Type.Stop), + ) + } + + @Ignore("reroute is not set at the very beginning of the route") + @Test + fun activeGuidanceReroute() = sdkTest(200_000) { + val dcShorRoute = RoutesProvider.dc_very_short(activity).apply { + setRouteAsOriginLocation() + }.toNavigationRoutes() + val dcShortReroute = DirectionsResponse.fromJson( + readRawFileText( + activity, + R.raw.reroute_response_dc_very_short + ) + ) + val offRouteLocationUpdate = mockLocationUpdatesRule.generateLocationUpdate { + latitude = dcShorRoute.first().waypoints!!.first().location().latitude() + 0.002 + longitude = dcShorRoute.first().waypoints!!.first().location().longitude() + } + mockWebServerRule.requestHandlers.add( + MockDirectionsRequestHandler( + profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC, + jsonResponse = dcShortReroute.toJson(), + expectedCoordinates = listOf( + Point.fromLngLat( + offRouteLocationUpdate.longitude, + offRouteLocationUpdate.latitude + ), + dcShorRoute.first().routeOptions.coordinatesList().last() + ), + relaxedExpectedCoordinates = true + ) + ) + val mapboxNavigation = createMapboxNavigation(activity) + + mapboxNavigation.setNavigationRoutes(dcShorRoute) + mapboxNavigation.startTripSession() + mapboxNavigation.routeProgressUpdates() + .first { it.currentState == RouteProgressState.TRACKING } + // go off route + mockLocationReplayerRule.loopUpdate(offRouteLocationUpdate, times = 5) + mapboxNavigation.routeProgressUpdates() + .first { it.currentState == RouteProgressState.TRACKING } + mockLocationReplayerRule.playRoute(dcShorRoute.first().directionsRoute) + mapboxNavigation.rawLocationUpdates().passed(150.0) + + // play new route + mockLocationReplayerRule.playbackSpeed(0.0) + mockLocationReplayerRule.playRoute(dcShortReroute.routes().first()) + mockLocationReplayerRule.playbackSpeed(2.0) + mapboxNavigation.routeProgressUpdates() + .first { it.currentState == RouteProgressState.COMPLETE } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventDepart(), + EventReroute(), + EventArrive(), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + @Ignore("alternative route event is not sent. FIXME add link") + @Test + fun activeGuidanceSwitchToAlternative() = sdkTest { + val dcShorWithAlternativeRoute = RoutesProvider.dc_short_with_alternative(activity).apply { + setRouteAsOriginLocation() + }.toNavigationRoutes() + val mapboxNavigation = createMapboxNavigation(activity) + mapboxNavigation.setNavigationRoutes(dcShorWithAlternativeRoute) + val alternativeToRide = dcShorWithAlternativeRoute[1] + + mapboxNavigation.startTripSession() + // play alternative route + mockLocationReplayerRule.playRoute(alternativeToRide.directionsRoute) + mockLocationReplayerRule.playbackSpeed(2.0) + + mapboxNavigation.routeProgressUpdates().sample(100) + .first { it.navigationRoute == alternativeToRide } + mapboxNavigation.routeProgressUpdates() + .first { it.currentState == RouteProgressState.COMPLETE } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventDepart(), + EventAlternativeRoute(), + EventArrive(), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + /** + * Reroute cancel in 150 (threshold is 100 meters) meters should have the full stack of events: + * - start nav session + * - departure + * - cancel + * - end nav session + */ +// @Ignore("doesn't work as expected") + @Test + fun activeGuidanceRouteCancelIn150Meters() = sdkTest { + val dcShorRoute = RoutesProvider.dc_very_short(activity).apply { + setRouteAsOriginLocation() + }.toNavigationRoutes() + val mapboxNavigation = createMapboxNavigation(activity) + mockLocationReplayerRule.playbackSpeed(2.0) + + mapboxNavigation.setNavigationRoutes(dcShorRoute) + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShorRoute.first().directionsRoute) + mapboxNavigation.routeProgressUpdates().first { it.distanceTraveled >= 150 } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventDepart(), + EventCancel(), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + /** + * Reroute cancel in 50 (threshold is 100 meters) meters should not have departure and cancel events: + * - start nav session + * - end nav session + */ + @Ignore("doesn't work as expected") + @Test + fun activeGuidanceRouteCancelIn50Meters() = sdkTest { + val dcShorRoute = RoutesProvider.dc_very_short(activity).apply { + setRouteAsOriginLocation() + }.toNavigationRoutes() + val mapboxNavigation = createMapboxNavigation(activity) + mapboxNavigation.setNavigationRoutes(dcShorRoute) + + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShorRoute.first().directionsRoute) + mapboxNavigation.routeProgressUpdates().first { it.distanceTraveled >= 50 } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + @Ignore("doesn't work as expected") + @Test + fun activeGuidance2LegRoutePassed() = sdkTest { + val dcShor2LegsRoute = RoutesProvider.dc_very_short_two_legs(activity).apply { + setRouteAsOriginLocation() + }.toNavigationRoutes() + val mapboxNavigation = createMapboxNavigation(activity) + mapboxNavigation.setNavigationRoutes(dcShor2LegsRoute) + + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShor2LegsRoute.first().directionsRoute) + mapboxNavigation.routeProgressUpdates() + .first { it.currentState == RouteProgressState.COMPLETE } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventDepart(), + EventArrive(), + EventDepart(), + EventArrive(), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + @Ignore("doesn't work as expected") + @Test + fun activeGuidance2LegRouteCanceledOnSecondLeg() = sdkTest { + val dcShor2LegsRoute = RoutesProvider.dc_very_short_two_legs(activity).apply { + setRouteAsOriginLocation() + }.toNavigationRoutes() + val mapboxNavigation = createMapboxNavigation(activity) + mapboxNavigation.setNavigationRoutes(dcShor2LegsRoute) + + mapboxNavigation.startTripSession() + mockLocationReplayerRule.playRoute(dcShor2LegsRoute.first().directionsRoute) + // traveled 50 meters on second leg + mapboxNavigation.routeProgressUpdates().first { + it.currentLegProgress!!.legIndex == 1 && it.currentLegProgress!!.distanceTraveled > 50 + } + mapboxNavigation.stopTripSession() + + verifyResult( + EventNavigationStateChanged(EventNavigationStateChanged.State.NavStarted), + EventDepart(), + EventArrive(), + EventDepart(), + EventCancel(), + EventNavigationStateChanged(EventNavigationStateChanged.State.NavEnded), + ) + } + + private suspend fun verifyResult(vararg expected: EventBase) { + waitForEventsBeSent(expected.size) { eventsAccumulatorRule.events.size } + val result = expected.asList().verifyEvents(eventsAccumulatorRule.events) + + check(result.isEmpty()) { + result.prettyErrors() + } + } + + private fun MockRoute.setRouteAsOriginLocation() { + mockLocationUpdatesRule.pushLocationUpdate( + mockLocationUpdatesRule.generateLocationUpdate { + latitude = this@setRouteAsOriginLocation.routeWaypoints.first().latitude() + longitude = this@setRouteAsOriginLocation.routeWaypoints.first().longitude() + } + ) + } +} diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/EventsAccumulatorRule.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/EventsAccumulatorRule.kt new file mode 100644 index 00000000000..1e2de38968f --- /dev/null +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/EventsAccumulatorRule.kt @@ -0,0 +1,50 @@ +package com.mapbox.navigation.instrumentation_tests.utils.events + +import com.mapbox.bindgen.Value +import com.mapbox.common.EventsServerOptions +import com.mapbox.common.EventsService +import com.mapbox.common.EventsServiceError +import com.mapbox.common.EventsServiceObserver +import com.mapbox.navigation.utils.internal.logE +import com.mapbox.navigator.Navigator +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +class EventsAccumulatorRule( + mapboxToken: String, +) : TestWatcher() { + + private val eventService = EventsService.getOrCreate( + EventsServerOptions( + mapboxToken, Navigator.getUserAgentFragment(), null + ) + ) + + private val eventsServiceObserver = object : EventsServiceObserver { + override fun didEncounterError(error: EventsServiceError, events: Value) { + logE(LOG_CATEGORY) { + "Occurred error [$error] when send events: $events" + } + } + + override fun didSendEvents(events: Value) { + _events.addAll(events.contents as List) + } + } + + private val _events = mutableListOf() + val events: List get() = _events + + private companion object { + private const val LOG_CATEGORY = "EventsAccumulatorRule" + } + + override fun starting(description: Description?) { + _events.clear() + eventService.registerObserver(eventsServiceObserver) + } + + override fun finished(description: Description?) { + eventService.unregisterObserver(eventsServiceObserver) + } +} diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/domain/EventsDomain.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/domain/EventsDomain.kt new file mode 100644 index 00000000000..2fa703afe53 --- /dev/null +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/domain/EventsDomain.kt @@ -0,0 +1,82 @@ +package com.mapbox.navigation.instrumentation_tests.utils.events.domain + +internal abstract class EventBase( + driverMode: DriverMode, +) { + abstract val event: String + val eventVersion: Double = 2.4 + val driverMode: String = when (driverMode) { + DriverMode.FreeDrive -> "freeDrive" + DriverMode.ActiveGuidance -> "trip" + } + + enum class DriverMode { + FreeDrive, + ActiveGuidance, + } +} + +internal abstract class EventBaseActiveGuidance( + +) : EventBase(DriverMode.ActiveGuidance) { + +} + +internal class EventFeedback( + driverMode: DriverMode, + val feedbackType: String, + val description: String, + val feedbackSubType: Array, +): EventBase(driverMode) { + override val event: String = "navigation.feedback" +} + +internal class EventFreeDrive( + eventType: Type, +) : EventBase(DriverMode.FreeDrive) { + override val event: String = "navigation.freeDrive" + val eventType: String = when (eventType) { + Type.Start -> "start" + Type.Stop -> "stop" + } + + enum class Type { + Start, + Stop, + } +} + +internal class EventNavigationStateChanged( + state: State, +) : EventBase(DriverMode.ActiveGuidance) { + override val event: String = "navigation.navigationStateChanged" + val state: String = when (state) { + State.NavStarted -> "navigation_started" + State.NavEnded -> "navigation_ended" + } + + enum class State { + NavStarted, + NavEnded, + } +} + +internal class EventDepart : EventBaseActiveGuidance() { + override val event: String = "navigation.depart" +} + +internal class EventArrive : EventBaseActiveGuidance() { + override val event: String = "navigation.arrive" +} + +internal class EventCancel : EventBaseActiveGuidance() { + override val event: String = "navigation.cancel" +} + +internal class EventAlternativeRoute: EventBaseActiveGuidance() { + override val event: String = "navigation.alternativeRoute" +} + +internal class EventReroute: EventBaseActiveGuidance() { + override val event: String = "navigation.reroute" +} diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/verify/EventsVerifyEx.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/verify/EventsVerifyEx.kt new file mode 100644 index 00000000000..b7cae47a43e --- /dev/null +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/events/verify/EventsVerifyEx.kt @@ -0,0 +1,42 @@ +package com.mapbox.navigation.instrumentation_tests.utils.events.verify + +import com.google.gson.Gson +import com.mapbox.bindgen.Value +import com.mapbox.navigation.instrumentation_tests.utils.events.domain.EventBase +import org.json.JSONObject + +internal fun EventBase.checkIfSubOf(value: Value): List { + val valueJson = JSONObject(value.toJson()) + val eventJson = JSONObject(Gson().toJson(this)) + + val nonValidValues = mutableListOf() + val subKeys = eventJson.keys() + subKeys.forEach { eventJsonKey -> + val eventData = eventJson.get(eventJsonKey) + val valueData = valueJson.get(eventJsonKey) + if (eventData != valueData) { + nonValidValues.add( + "Event [$event], key [$eventJsonKey] does not match data in `event` [$eventData] and `value` [$valueData]" + ) + } + } + return nonValidValues +} + +internal fun Collection.verifyEvents(events: List): List { + if (this.size != events.size) { + return listOf( + """--- + Events lists must have same sizes: expected List is ${this.size} actual List is ${events.size} + expected: ${this.joinToString { it.event }} + actual: ${events.joinToString { (it.contents as Map)["event"]?.contents as String }} + ---""" + ) + } + + return this.foldIndexed(mutableListOf()) { index, accumulateList, eventBase -> + accumulateList.apply { + addAll(eventBase.checkIfSubOf(events[index])) + } + } +} diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/http/EventsRequestHandle.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/http/EventsRequestHandle.kt new file mode 100644 index 00000000000..9b3500d5d7f --- /dev/null +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/http/EventsRequestHandle.kt @@ -0,0 +1,14 @@ +package com.mapbox.navigation.instrumentation_tests.utils.http + +import com.mapbox.navigation.testing.ui.http.MockRequestHandler +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest + +class EventsRequestHandle : MockRequestHandler { + override fun handle(request: RecordedRequest): MockResponse? { + if (request.path!!.startsWith("/events/v2")) { + return MockResponse().setResponseCode(204) + } + return null + } +} diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/MockLocationReplayer.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/MockLocationReplayer.kt index 079cd072364..dd01336f4b2 100644 --- a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/MockLocationReplayer.kt +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/MockLocationReplayer.kt @@ -77,6 +77,10 @@ class MockLocationReplayerRule(mockLocationUpdatesRule: MockLocationUpdatesRule) clearEvents() } } + + fun playbackSpeed(scale: Double) { + mapboxReplayer?.playbackSpeed(scale) + } } fun Location.setUpLocation(event: ReplayEventUpdateLocation) { diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/TurfUtils.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/TurfUtils.kt new file mode 100644 index 00000000000..f1070a9b1ae --- /dev/null +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/location/TurfUtils.kt @@ -0,0 +1,10 @@ +package com.mapbox.navigation.instrumentation_tests.utils.location + +import com.mapbox.geojson.Point +import com.mapbox.turf.TurfConstants +import com.mapbox.turf.TurfMeasurement + +internal operator fun Int.compareTo(pointsDiff: Pair): Int { + val (p1, p2) = pointsDiff + return this - TurfMeasurement.distance(p1, p2, TurfConstants.UNIT_METERS).toInt() +} diff --git a/instrumentation-tests/src/main/res/raw/route_response_dc_very_short_two_legs.json b/instrumentation-tests/src/main/res/raw/route_response_dc_very_short_two_legs.json index d8d063892d9..fdadd90a3c3 100644 --- a/instrumentation-tests/src/main/res/raw/route_response_dc_very_short_two_legs.json +++ b/instrumentation-tests/src/main/res/raw/route_response_dc_very_short_two_legs.json @@ -1 +1,388 @@ -{"routes":[{"weight_name":"auto","weight":40.077,"duration":28.029,"distance":175.999,"legs":[{"via_waypoints":[],"admins":[{"iso_3166_1_alpha3":"USA","iso_3166_1":"US"}],"weight":13.988,"duration":9.967,"steps":[{"intersections":[{"entry":[true],"bearings":[0],"duration":8.52,"mapbox_streets_v8":{"class":"secondary"},"is_urban":true,"admin_index":0,"out":0,"weight":10.224,"geometry_index":0,"location":[-77.031957,38.894721]},{"bearings":[1,180],"entry":[true,false],"in":1,"turn_weight":2,"lanes":[{"indications":["left"],"valid":false,"active":false},{"indications":["straight"],"valid_indication":"straight","valid":true,"active":false},{"indications":["straight"],"valid_indication":"straight","valid":true,"active":false},{"indications":["straight"],"valid_indication":"straight","valid":true,"active":true}],"turn_duration":0.007,"mapbox_streets_v8":{"class":"secondary"},"is_urban":true,"admin_index":0,"out":0,"geometry_index":1,"location":[-77.031952,38.895356]}],"maneuver":{"type":"depart","instruction":"Drive north on 14th Street Northwest.","bearing_after":0,"bearing_before":0,"location":[-77.031957,38.894721]},"name":"14th Street Northwest","duration":9.967,"distance":82.999,"driving_side":"right","weight":13.988,"mode":"driving","geometry":"ag}diAh`t|qCuf@IoEC"},{"intersections":[{"bearings":[181],"entry":[true],"in":0,"admin_index":0,"geometry_index":2,"location":[-77.03195,38.89546]}],"maneuver":{"type":"arrive","instruction":"You have arrived at your destination.","bearing_after":0,"bearing_before":1,"location":[-77.03195,38.89546]},"name":"14th Street Northwest","duration":0,"distance":0,"driving_side":"right","weight":0,"mode":"driving","geometry":"gu~diAz_t|qC??"}],"distance":82.999,"summary":"14th Street Northwest"},{"via_waypoints":[],"admins":[{"iso_3166_1_alpha3":"USA","iso_3166_1":"US"}],"weight":26.089,"duration":18.062,"steps":[{"intersections":[{"bearings":[91],"entry":[true],"mapbox_streets_v8":{"class":"secondary"},"is_urban":true,"admin_index":0,"out":0,"geometry_index":0,"location":[-77.03195,38.89546]},{"lanes":[{"indications":["left"],"valid":false,"active":false},{"indications":["straight"],"valid_indication":"straight","valid":true,"active":false},{"indications":["straight"],"valid_indication":"straight","valid":true,"active":false},{"indications":["straight"],"valid_indication":"straight","valid":true,"active":true}],"location":[-77.03195,38.89546],"geometry_index":1,"admin_index":0,"weight":12.307,"is_urban":true,"mapbox_streets_v8":{"class":"primary"},"turn_duration":4.105,"turn_weight":9,"duration":6.805,"bearings":[91,271],"out":0,"in":1,"entry":[true,false]},{"entry":[true,false],"in":1,"bearings":[90,271],"duration":6.907,"turn_duration":0.007,"mapbox_streets_v8":{"class":"primary"},"is_urban":true,"admin_index":0,"out":0,"weight":8.453,"geometry_index":2,"location":[-77.031746,38.895456]},{"bearings":[89,270],"entry":[true,false],"in":1,"mapbox_streets_v8":{"class":"primary"},"is_urban":true,"admin_index":0,"out":0,"geometry_index":3,"location":[-77.031214,38.895455]}],"maneuver":{"type":"depart","instruction":"Drive east on 14th Street Northwest.","bearing_after":91,"bearing_before":0,"location":[-77.03195,38.89546]},"name":"14th Street Northwest","duration":18.062,"distance":93,"driving_side":"right","weight":26.089,"mode":"driving","geometry":"gu~diAz_t|qC??FwK@g`@CsPVmB"},{"intersections":[{"bearings":[273],"entry":[true],"in":0,"admin_index":0,"geometry_index":5,"location":[-77.030877,38.895445]}],"maneuver":{"type":"arrive","instruction":"You have arrived at your destination.","bearing_after":0,"bearing_before":93,"location":[-77.030877,38.895445]},"name":"14th Street Northwest","duration":0,"distance":0,"driving_side":"right","weight":0,"mode":"driving","geometry":"it~diAx|q|qC??"}],"distance":93,"summary":"14th Street Northwest"}],"geometry":"ag}diAh`t|qCuf@IoEC??FwK@g`@CsPVmB"}],"waypoints":[{"distance":2.923,"name":"14th Street Northwest","location":[-77.031957,38.894721]},{"distance":4.653,"name":"14th Street Northwest","location":[-77.03195,38.89546]},{"distance":4.203,"name":"Pennsylvania Avenue Northwest","location":[-77.030877,38.895445]}],"code":"Ok","uuid":"-gwFFMMQec5DWP_57ZYK6p2R0FupsxNYJfNCP6IICk2zWNGRGsMZvg=="} \ No newline at end of file +{ + "routes": [ + { + "weight_name": "auto", + "weight": 40.077, + "duration": 28.029, + "distance": 175.999, + "legs": [ + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "weight": 13.988, + "duration": 9.967, + "steps": [ + { + "intersections": [ + { + "entry": [ + true + ], + "bearings": [ + 0 + ], + "duration": 8.52, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 10.224, + "geometry_index": 0, + "location": [ + -77.031957, + 38.894721 + ] + }, + { + "bearings": [ + 1, + 180 + ], + "entry": [ + true, + false + ], + "in": 1, + "turn_weight": 2, + "lanes": [ + { + "indications": [ + "left" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 1, + "location": [ + -77.031952, + 38.895356 + ] + } + ], + "maneuver": { + "type": "depart", + "instruction": "Drive north on 14th Street Northwest.", + "bearing_after": 0, + "bearing_before": 0, + "location": [ + -77.031957, + 38.894721 + ] + }, + "name": "14th Street Northwest", + "duration": 9.967, + "distance": 82.999, + "driving_side": "right", + "weight": 13.988, + "mode": "driving", + "geometry": "ag}diAh`t|qCuf@IoEC" + }, + { + "intersections": [ + { + "bearings": [ + 181 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 2, + "location": [ + -77.03195, + 38.89546 + ] + } + ], + "maneuver": { + "type": "arrive", + "instruction": "You have arrived at your destination.", + "bearing_after": 0, + "bearing_before": 1, + "location": [ + -77.03195, + 38.89546 + ] + }, + "name": "14th Street Northwest", + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "gu~diAz_t|qC??" + } + ], + "distance": 82.999, + "summary": "14th Street Northwest" + }, + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "weight": 26.089, + "duration": 18.062, + "steps": [ + { + "intersections": [ + { + "bearings": [ + 91 + ], + "entry": [ + true + ], + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 0, + "location": [ + -77.03195, + 38.89546 + ] + }, + { + "lanes": [ + { + "indications": [ + "left" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "location": [ + -77.03195, + 38.89546 + ], + "geometry_index": 1, + "admin_index": 0, + "weight": 12.307, + "is_urban": true, + "mapbox_streets_v8": { + "class": "primary" + }, + "turn_duration": 4.105, + "turn_weight": 9, + "duration": 6.805, + "bearings": [ + 91, + 271 + ], + "out": 0, + "in": 1, + "entry": [ + true, + false + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 90, + 271 + ], + "duration": 6.907, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "primary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 8.453, + "geometry_index": 2, + "location": [ + -77.031746, + 38.895456 + ] + }, + { + "bearings": [ + 89, + 270 + ], + "entry": [ + true, + false + ], + "in": 1, + "mapbox_streets_v8": { + "class": "primary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 3, + "location": [ + -77.031214, + 38.895455 + ] + } + ], + "maneuver": { + "type": "depart", + "instruction": "Drive east on 14th Street Northwest.", + "bearing_after": 91, + "bearing_before": 0, + "location": [ + -77.03195, + 38.89546 + ] + }, + "name": "14th Street Northwest", + "duration": 18.062, + "distance": 93, + "driving_side": "right", + "weight": 26.089, + "mode": "driving", + "geometry": "gu~diAz_t|qC??FwK@g`@CsPVmB" + }, + { + "intersections": [ + { + "bearings": [ + 273 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 5, + "location": [ + -77.030877, + 38.895445 + ] + } + ], + "maneuver": { + "type": "arrive", + "instruction": "You have arrived at your destination.", + "bearing_after": 0, + "bearing_before": 93, + "location": [ + -77.030877, + 38.895445 + ] + }, + "name": "14th Street Northwest", + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "it~diAx|q|qC??" + } + ], + "distance": 93, + "summary": "14th Street Northwest" + } + ], + "geometry": "ag}diAh`t|qCuf@IoEC??FwK@g`@CsPVmB" + } + ], + "waypoints": [ + { + "distance": 2.923, + "name": "14th Street Northwest", + "location": [ + -77.031957, + 38.894721 + ] + }, + { + "distance": 4.653, + "name": "14th Street Northwest", + "location": [ + -77.03195, + 38.89546 + ] + }, + { + "distance": 4.203, + "name": "Pennsylvania Avenue Northwest", + "location": [ + -77.030877, + 38.895445 + ] + } + ], + "code": "Ok", + "uuid": "-gwFFMMQec5DWP_57ZYK6p2R0FupsxNYJfNCP6IICk2zWNGRGsMZvg==" +} \ No newline at end of file diff --git a/libnavigation-copilot/src/main/java/com/mapbox/navigation/copilot/MapboxCopilotImpl.kt b/libnavigation-copilot/src/main/java/com/mapbox/navigation/copilot/MapboxCopilotImpl.kt index c43c6d7011d..ac7d5d5f4dc 100644 --- a/libnavigation-copilot/src/main/java/com/mapbox/navigation/copilot/MapboxCopilotImpl.kt +++ b/libnavigation-copilot/src/main/java/com/mapbox/navigation/copilot/MapboxCopilotImpl.kt @@ -30,7 +30,7 @@ import com.mapbox.navigation.core.internal.HistoryRecordingStateChangeObserver import com.mapbox.navigation.core.internal.extensions.registerHistoryRecordingStateChangeObserver import com.mapbox.navigation.core.internal.extensions.retrieveCopilotHistoryRecorder import com.mapbox.navigation.core.internal.extensions.unregisterHistoryRecordingStateChangeObserver -import com.mapbox.navigation.core.internal.telemetry.UserFeedback +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackInternal import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback import com.mapbox.navigation.core.internal.telemetry.registerUserFeedbackCallback import com.mapbox.navigation.core.internal.telemetry.unregisterUserFeedbackCallback @@ -131,7 +131,7 @@ internal class MapboxCopilotImpl( * start */ fun start() { - registerUserFeedbackCallback(userFeedbackCallback) + registerUserFeedbackCallback(mapboxNavigation, userFeedbackCallback) if (isMapboxNavigationAppSetup) { MapboxNavigationApp.lifecycleOwner.lifecycle.addObserver( foregroundBackgroundLifecycleObserver @@ -148,7 +148,7 @@ internal class MapboxCopilotImpl( * stop */ fun stop() { - unregisterUserFeedbackCallback(userFeedbackCallback) + unregisterUserFeedbackCallback(mapboxNavigation, userFeedbackCallback) if (isMapboxNavigationAppSetup) { MapboxNavigationApp.lifecycleOwner.lifecycle.removeObserver( foregroundBackgroundLifecycleObserver @@ -217,12 +217,12 @@ internal class MapboxCopilotImpl( copilotHistoryRecorder.pushHistory(eventType, eventJson) } - private fun pushFeedbackEvent(userFeedback: UserFeedback) { - val lat = userFeedback.location.latitude() - val lng = userFeedback.location.longitude() - val feedbackId = userFeedback.feedbackId - val feedbackType = userFeedback.feedbackType - val feedbackSubType = userFeedback.feedbackSubType?.toHashSet().orEmpty() + private fun pushFeedbackEvent(userFeedbackInternal: UserFeedbackInternal) { + val lat = userFeedbackInternal.location.latitude() + val lng = userFeedbackInternal.location.longitude() + val feedbackId = userFeedbackInternal.feedbackId + val feedbackType = userFeedbackInternal.feedbackType + val feedbackSubType = userFeedbackInternal.feedbackSubType?.toHashSet().orEmpty() val feedbackEvent = NavFeedbackSubmitted( feedbackId, feedbackType, diff --git a/libnavigation-copilot/src/test/java/com/mapbox/navigation/copilot/MapboxCopilotImplTest.kt b/libnavigation-copilot/src/test/java/com/mapbox/navigation/copilot/MapboxCopilotImplTest.kt index 0cd5d263f4c..634dda049ed 100644 --- a/libnavigation-copilot/src/test/java/com/mapbox/navigation/copilot/MapboxCopilotImplTest.kt +++ b/libnavigation-copilot/src/test/java/com/mapbox/navigation/copilot/MapboxCopilotImplTest.kt @@ -25,7 +25,7 @@ import com.mapbox.navigation.core.internal.HistoryRecordingStateChangeObserver import com.mapbox.navigation.core.internal.extensions.registerHistoryRecordingStateChangeObserver import com.mapbox.navigation.core.internal.extensions.retrieveCopilotHistoryRecorder import com.mapbox.navigation.core.internal.extensions.unregisterHistoryRecordingStateChangeObserver -import com.mapbox.navigation.core.internal.telemetry.UserFeedback +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackInternal import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback import com.mapbox.navigation.core.internal.telemetry.registerUserFeedbackCallback import com.mapbox.navigation.core.internal.telemetry.unregisterUserFeedbackCallback @@ -505,9 +505,9 @@ class MapboxCopilotImplTest { historyRecordingStateChangeObserver.captured.onShouldStartRecording( freeDriveHistoryRecordingSessionState ) - val mockedUserFeedback = mockk(relaxed = true) + val mockedUserFeedbackInternal = mockk(relaxed = true) - userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedback) + userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedbackInternal) verify(exactly = 1) { mockedHistoryRecorder.pushHistory(NAV_FEEDBACK_SUBMITTED_EVENT_NAME, any()) @@ -539,9 +539,9 @@ class MapboxCopilotImplTest { historyRecordingStateChangeObserver.captured.onShouldStartRecording( activeGuidanceHistoryRecordingSessionState ) - val mockedUserFeedback = mockk(relaxed = true) + val mockedUserFeedbackInternal = mockk(relaxed = true) - userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedback) + userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedbackInternal) verify(exactly = 1) { mockedHistoryRecorder.pushHistory(NAV_FEEDBACK_SUBMITTED_EVENT_NAME, any()) @@ -630,8 +630,8 @@ class MapboxCopilotImplTest { historyRecordingStateChangeObserver.captured.onShouldStartRecording( mockedHistoryRecordingSessionState ) - val mockedUserFeedback = mockk(relaxed = true) - userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedback) + val mockedUserFeedbackInternal = mockk(relaxed = true) + userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedbackInternal) historyRecordingStateChangeObserver.captured.onShouldStopRecording( mockedHistoryRecordingSessionState ) @@ -761,8 +761,8 @@ class MapboxCopilotImplTest { historyRecordingStateChangeObserver.captured.onShouldStartRecording( mockedHistoryRecordingSessionState ) - val mockedUserFeedback = mockk(relaxed = true) - userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedback) + val mockedUserFeedbackInternal = mockk(relaxed = true) + userFeedbackCallback.captured.onNewUserFeedback(mockedUserFeedbackInternal) historyRecordingStateChangeObserver.captured.onShouldStopRecording( mockedHistoryRecordingSessionState ) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt index bdee4dfdd50..1f32081bc40 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt @@ -57,7 +57,9 @@ import com.mapbox.navigation.core.history.MapboxHistoryReader import com.mapbox.navigation.core.history.MapboxHistoryRecorder import com.mapbox.navigation.core.internal.ReachabilityService import com.mapbox.navigation.core.internal.telemetry.CustomEvent +import com.mapbox.navigation.core.internal.telemetry.TelemetryAndroidAutoInterface import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackSubscriber import com.mapbox.navigation.core.internal.utils.InternalUtils import com.mapbox.navigation.core.internal.utils.ModuleParams import com.mapbox.navigation.core.internal.utils.isInternalImplementation @@ -88,11 +90,13 @@ import com.mapbox.navigation.core.routealternatives.RouteAlternativesRequestCall import com.mapbox.navigation.core.routeoptions.RouteOptionsUpdater import com.mapbox.navigation.core.routerefresh.RouteRefreshController import com.mapbox.navigation.core.routerefresh.RouteRefreshControllerProvider -import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry +import com.mapbox.navigation.core.telemetry.ApplicationLifecycleMonitor +import com.mapbox.navigation.core.telemetry.NavigationTelemetry import com.mapbox.navigation.core.telemetry.events.FeedbackEvent import com.mapbox.navigation.core.telemetry.events.FeedbackHelper import com.mapbox.navigation.core.telemetry.events.FeedbackMetadata import com.mapbox.navigation.core.telemetry.events.FeedbackMetadataWrapper +import com.mapbox.navigation.core.telemetry.events.UserFeedback import com.mapbox.navigation.core.trip.service.TripService import com.mapbox.navigation.core.trip.session.BannerInstructionsObserver import com.mapbox.navigation.core.trip.session.LegIndexUpdatedCallback @@ -119,7 +123,6 @@ import com.mapbox.navigation.core.trip.session.eh.GraphAccessor import com.mapbox.navigation.core.trip.session.eh.RoadObjectMatcher import com.mapbox.navigation.core.trip.session.eh.RoadObjectsStore import com.mapbox.navigation.metrics.MapboxMetricsReporter -import com.mapbox.navigation.metrics.internal.TelemetryUtilsDelegate import com.mapbox.navigation.navigator.internal.MapboxNativeNavigator import com.mapbox.navigation.navigator.internal.NavigatorLoader import com.mapbox.navigation.navigator.internal.router.RouterInterfaceAdapter @@ -136,6 +139,7 @@ import com.mapbox.navigator.ConfigHandle import com.mapbox.navigator.ElectronicHorizonOptions import com.mapbox.navigator.FallbackVersionsObserver import com.mapbox.navigator.IncidentsOptions +import com.mapbox.navigator.Navigator import com.mapbox.navigator.NavigatorConfig import com.mapbox.navigator.PollingConfig import com.mapbox.navigator.RouterInterface @@ -152,7 +156,6 @@ import kotlinx.coroutines.sync.withLock import java.lang.reflect.Field import java.util.Locale -private const val MAPBOX_NAVIGATION_USER_AGENT_BASE = "mapbox-navigation-android" private const val MAPBOX_NAVIGATION_TOKEN_EXCEPTION_ROUTER = "You need to provide an access token in NavigationOptions in order to use the default " + "Router." @@ -251,6 +254,11 @@ class MapboxNavigation @VisibleForTesting internal constructor( private val mainJobController = threadController.getMainScopeAndRootJob() private val directionsSession: DirectionsSession private var navigator: MapboxNativeNavigator + private var navigationTelemetry: NavigationTelemetry + internal val telemetryAndroidAutoInterface: TelemetryAndroidAutoInterface + get() = navigationTelemetry + internal val userFeedbackSubscriber: UserFeedbackSubscriber + get() = navigationTelemetry private var historyRecorderHandles: NavigatorLoader.HistoryRecorderHandles private val tripService: TripService private val tripSession: TripSession @@ -464,6 +472,11 @@ class MapboxNavigation @VisibleForTesting internal constructor( } else { RouterInterfaceAdapter(moduleRouter, ::getNavigationRoutes) }, + NavigationComponentProvider.createEventsMetadataInterface( + navigationOptions.applicationContext, + ApplicationLifecycleMonitor(navigationOptions.applicationContext), + navigationOptions.eventsAppMetadata + ), ) assignHistoryRecorders() navigationSession = NavigationComponentProvider.createNavigationSession() @@ -530,24 +543,19 @@ class MapboxNavigation @VisibleForTesting internal constructor( arrivalProgressObserver ) + navigationTelemetry = NavigationTelemetry(tripSession) { navigator.telemetry } + ifNonNull(accessToken) { token -> - runInTelemetryContext { telemetry -> - logD( - "MapboxMetricsReporter.init from MapboxNavigation main", - telemetry.LOG_CATEGORY - ) - MapboxMetricsReporter.init( - navigationOptions.applicationContext, - token, - obtainUserAgent() - ) - MapboxMetricsReporter.toggleLogging(navigationOptions.isDebugLoggingEnabled) - telemetry.initialize( - this, - navigationOptions, - MapboxMetricsReporter, - ) - } + logD( + "MapboxMetricsReporter.init from MapboxNavigation main", + LOG_CATEGORY + ) + MapboxMetricsReporter.init( + navigationOptions.applicationContext, + token, + Navigator.getUserAgentFragment(), + ) + MapboxMetricsReporter.toggleLogging(navigationOptions.isDebugLoggingEnabled) } val routeOptionsProvider = RouteOptionsUpdater() @@ -1242,9 +1250,6 @@ class MapboxNavigation @VisibleForTesting internal constructor( @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) routeRefreshController.destroy() routesPreviewController.unregisterAllRoutesPreviewObservers() - runInTelemetryContext { telemetry -> - telemetry.destroy(this@MapboxNavigation) - } threadController.cancelAllNonUICoroutines() threadController.cancelAllUICoroutines() ifNonNull(reachabilityObserverId) { @@ -1620,12 +1625,15 @@ class MapboxNavigation @VisibleForTesting internal constructor( feedbackSubType: Array? = emptyArray(), ) { postUserFeedback( - feedbackType, - description, - feedbackSource, - screenshot, - feedbackSubType, - feedbackMetadata = null, + UserFeedback.Builder( + feedbackType, + description, + ).apply { + // FIXME encoded screenshot to bitmap +// screenshot(screenshot) + feedbackSubTypes(feedbackSubType ?: emptyArray()) + } + .build(), userFeedbackCallback = null, ) } @@ -1653,45 +1661,23 @@ class MapboxNavigation @VisibleForTesting internal constructor( @ExperimentalPreviewMapboxNavigationAPI @JvmOverloads fun postUserFeedback( - feedbackType: String, - description: String, - @FeedbackEvent.Source feedbackSource: String, - screenshot: String, - feedbackSubType: Array? = emptyArray(), - feedbackMetadata: FeedbackMetadata, + userFeedback: UserFeedback, ) { postUserFeedback( - feedbackType, - description, - feedbackSource, - screenshot, - feedbackSubType, - feedbackMetadata, + userFeedback = userFeedback, userFeedbackCallback = null, ) } @ExperimentalPreviewMapboxNavigationAPI internal fun postUserFeedback( - feedbackType: String, - description: String, - @FeedbackEvent.Source feedbackSource: String, - screenshot: String, - feedbackSubType: Array?, - feedbackMetadata: FeedbackMetadata?, + userFeedback: UserFeedback, userFeedbackCallback: UserFeedbackCallback?, ) { - runInTelemetryContext { telemetry -> - telemetry.postUserFeedback( - feedbackType, - description, - feedbackSource, - screenshot, - feedbackSubType, - feedbackMetadata, - userFeedbackCallback, - ) - } + navigationTelemetry.postUserFeedback( + userFeedback, + userFeedbackCallback + ) } @ExperimentalPreviewMapboxNavigationAPI @@ -1700,30 +1686,21 @@ class MapboxNavigation @VisibleForTesting internal constructor( @CustomEvent.Type customEventType: String, customEventVersion: String, ) { - runInTelemetryContext { telemetry -> - telemetry.postCustomEvent( - payload = payload, - customEventType = customEventType, - customEventVersion = customEventVersion - ) - } + navigationTelemetry.postCustomEvent( + customEventType, + customEventVersion, + payload, + ) } /** - * Provides wrapper of [FeedbackMetadata]. [FeedbackMetadata] is used to send deferred + * Provides [FeedbackMetadata], that is used to send deferred * feedback attached to passed location. It contains data (like location, time) when method is * invoked. - * - * Note: method throws [IllegalStateException] if trips session is not - * started ([startTripSession]) or telemetry is disabled. */ @ExperimentalPreviewMapboxNavigationAPI fun provideFeedbackMetadataWrapper(): FeedbackMetadataWrapper = - runInTelemetryContext { telemetry -> - telemetry.provideFeedbackMetadataWrapper() - } ?: throw java.lang.IllegalStateException( - "To get FeedbackMetadataWrapper Telemetry must be enabled" - ) + navigationTelemetry.provideFeedbackMetadataWrapper() /** * Start observing alternatives routes for a trip session via [RouteAlternativesObserver]. @@ -2001,6 +1978,11 @@ class MapboxNavigation @VisibleForTesting internal constructor( } else { RouterInterfaceAdapter(moduleRouter, ::getNavigationRoutes) }, + NavigationComponentProvider.createEventsMetadataInterface( + navigationOptions.applicationContext, + ApplicationLifecycleMonitor(navigationOptions.applicationContext), + navigationOptions.eventsAppMetadata + ), ) assignHistoryRecorders() @@ -2028,16 +2010,8 @@ class MapboxNavigation @VisibleForTesting internal constructor( } } - private inline fun runInTelemetryContext(func: (MapboxNavigationTelemetry) -> T): T? { - return if (TelemetryUtilsDelegate.getEventsCollectionState()) { - func(MapboxNavigationTelemetry) - } else { - null - } - } - - private fun obtainUserAgent(): String { - return "$MAPBOX_NAVIGATION_USER_AGENT_BASE/${BuildConfig.MAPBOX_NAVIGATION_VERSION_NAME}" + private fun cancelReroute() { + rerouteController?.interrupt() } private fun monitorNotificationActionButton(channel: ReceiveChannel) { diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt index 10ed8326153..3ee8771db89 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt @@ -2,6 +2,7 @@ package com.mapbox.navigation.core import android.content.Context import com.mapbox.navigation.base.internal.NavigationRouterV2 +import com.mapbox.navigation.base.options.EventsAppMetadata import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.base.options.RerouteOptions import com.mapbox.navigation.base.trip.notification.TripNotification @@ -15,6 +16,8 @@ import com.mapbox.navigation.core.preview.RoutesPreviewController import com.mapbox.navigation.core.reroute.InternalRerouteController import com.mapbox.navigation.core.reroute.MapboxRerouteController import com.mapbox.navigation.core.routeoptions.RouteOptionsUpdater +import com.mapbox.navigation.core.telemetry.ApplicationLifecycleMonitor +import com.mapbox.navigation.core.telemetry.EventsMetadataInterfaceImpl import com.mapbox.navigation.core.trip.service.MapboxTripService import com.mapbox.navigation.core.trip.service.TripService import com.mapbox.navigation.core.trip.session.MapboxTripSession @@ -26,6 +29,7 @@ import com.mapbox.navigation.navigator.internal.MapboxNativeNavigator import com.mapbox.navigation.navigator.internal.MapboxNativeNavigatorImpl import com.mapbox.navigation.utils.internal.ThreadController import com.mapbox.navigator.ConfigHandle +import com.mapbox.navigator.EventsMetadataInterface import com.mapbox.navigator.HistoryRecorderHandle import com.mapbox.navigator.RouterInterface import com.mapbox.navigator.TilesConfig @@ -43,12 +47,14 @@ internal object NavigationComponentProvider { tilesConfig: TilesConfig, accessToken: String, router: RouterInterface, + eventsMetadataInterface: EventsMetadataInterface, ): MapboxNativeNavigator = MapboxNativeNavigatorImpl.create( config, historyRecorderComposite, tilesConfig, accessToken, router, + eventsMetadataInterface, ) fun createTripService( @@ -121,6 +127,16 @@ internal object NavigationComponentProvider { fun createRoutesCacheClearer(): RoutesCacheClearer = RoutesCacheClearer() + fun createEventsMetadataInterface( + appContext: Context, + lifecycleMonitor: ApplicationLifecycleMonitor, + eventsAppMetadata: EventsAppMetadata?, + ): EventsMetadataInterface = EventsMetadataInterfaceImpl( + appContext, + lifecycleMonitor, + eventsAppMetadata + ) + fun createRerouteController( directionsSession: DirectionsSession, tripSession: TripSession, diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryAndroidAutoInterface.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryAndroidAutoInterface.kt new file mode 100644 index 00000000000..d641f7e16db --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryAndroidAutoInterface.kt @@ -0,0 +1,7 @@ +package com.mapbox.navigation.core.internal.telemetry + +import com.mapbox.navigation.core.internal.telemetry.event.AndroidAutoEvent + +interface TelemetryAndroidAutoInterface { + fun postAndroidAutoEvent(event: AndroidAutoEvent) +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryEx.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryEx.kt index 8171e834a3c..239ca5d7b25 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryEx.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/TelemetryEx.kt @@ -2,45 +2,23 @@ package com.mapbox.navigation.core.internal.telemetry -import android.location.Location -import android.os.Build import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry import com.mapbox.navigation.core.telemetry.events.FeedbackEvent import com.mapbox.navigation.core.telemetry.events.FeedbackHelper import com.mapbox.navigation.core.telemetry.events.FeedbackMetadata import com.mapbox.navigation.core.telemetry.events.FeedbackMetadataWrapper -import com.mapbox.navigation.core.telemetry.events.TelemetryLocation - -internal fun List.toTelemetryLocations(): Array { - return Array(size) { get(it).toTelemetryLocation() } -} - -internal fun Location.toTelemetryLocation(): TelemetryLocation { - return TelemetryLocation( - latitude, - longitude, - speed, - bearing, - altitude, - time.toString(), - accuracy, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - verticalAccuracyMeters - } else { - 0f - }, - ) -} +import com.mapbox.navigation.core.telemetry.events.UserFeedback /** * Register a [UserFeedbackCallback] to be notified when a new user feedback is posted. * * @param userFeedbackCallback UserFeedbackCallback */ -fun registerUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { - MapboxNavigationTelemetry.registerUserFeedbackCallback(userFeedbackCallback) +fun registerUserFeedbackCallback( + mapboxNavigation: MapboxNavigation, userFeedbackCallback: UserFeedbackCallback +) { + mapboxNavigation.userFeedbackSubscriber.registerUserFeedbackCallback(userFeedbackCallback) } /** @@ -48,8 +26,10 @@ fun registerUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { * * @param userFeedbackCallback UserFeedbackCallback */ -fun unregisterUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { - MapboxNavigationTelemetry.unregisterUserFeedbackCallback(userFeedbackCallback) +fun unregisterUserFeedbackCallback( + mapboxNavigation: MapboxNavigation, userFeedbackCallback: UserFeedbackCallback +) { + mapboxNavigation.userFeedbackSubscriber.unregisterUserFeedbackCallback(userFeedbackCallback) } /** @@ -77,21 +57,11 @@ fun unregisterUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { @ExperimentalPreviewMapboxNavigationAPI @JvmOverloads fun MapboxNavigation.postUserFeedback( - feedbackType: String, - description: String, - @FeedbackEvent.Source feedbackSource: String, - screenshot: String, - feedbackSubType: Array? = emptyArray(), - feedbackMetadata: FeedbackMetadata? = null, + userFeedback: UserFeedback, userFeedbackCallback: UserFeedbackCallback, ) { postUserFeedback( - feedbackType, - description, - feedbackSource, - screenshot, - feedbackSubType, - feedbackMetadata, + userFeedback, userFeedbackCallback, ) } @@ -104,3 +74,6 @@ fun MapboxNavigation.sendCustomEvent( ) { postCustomEvent(payload, customEventType, customEventVersion) } + +fun MapboxNavigation.telemetryAndroidAutoInterface(): TelemetryAndroidAutoInterface = + telemetryAndroidAutoInterface diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackCallback.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackCallback.kt index a1c806ba13b..12a03a1f03d 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackCallback.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackCallback.kt @@ -9,7 +9,7 @@ fun interface UserFeedbackCallback { /** * Notifies that a new user feedback has been posted. * - * @param userFeedback the posted [UserFeedback] + * @param userFeedbackInternal the posted [UserFeedbackInternal] */ - fun onNewUserFeedback(userFeedback: UserFeedback) + fun onNewUserFeedback(userFeedbackInternal: UserFeedbackInternal) } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedback.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackInternal.kt similarity index 79% rename from libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedback.kt rename to libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackInternal.kt index d8a8e278652..66cee6d47dc 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedback.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackInternal.kt @@ -3,6 +3,7 @@ package com.mapbox.navigation.core.internal.telemetry import com.mapbox.geojson.Point import com.mapbox.navigation.core.MapboxNavigation import com.mapbox.navigation.core.telemetry.events.FeedbackEvent +import com.mapbox.navigation.core.telemetry.events.UserFeedback /** * Class for user feedbacks, contains properties that were passed to @@ -16,26 +17,36 @@ import com.mapbox.navigation.core.telemetry.events.FeedbackEvent * @property feedbackSubType array of [FeedbackEvent.SubType] and/or custom feedback subtypes * @property location user location when the feedback event was posted */ -class UserFeedback internal constructor( +class UserFeedbackInternal internal constructor( val feedbackId: String, val feedbackType: String, - @FeedbackEvent.Source val source: String, val description: String, - val screenshot: String?, val feedbackSubType: Array?, val location: Point, ) { + + companion object { + fun UserFeedback.toInternal( + feedbackId: String, + location: Point, + ): UserFeedbackInternal = UserFeedbackInternal( + feedbackId = feedbackId, + feedbackType = feedbackType, + description = description, + feedbackSubType = feedbackSubTypes, + location = location, + ) + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as UserFeedback + other as UserFeedbackInternal if (feedbackId != other.feedbackId) return false if (feedbackType != other.feedbackType) return false - if (source != other.source) return false if (description != other.description) return false - if (screenshot != other.screenshot) return false if (!feedbackSubType.contentEquals(other.feedbackSubType)) return false if (location != other.location) return false @@ -45,9 +56,7 @@ class UserFeedback internal constructor( override fun hashCode(): Int { var result = feedbackId.hashCode() result = 31 * result + feedbackType.hashCode() - result = 31 * result + source.hashCode() result = 31 * result + description.hashCode() - result = 31 * result + screenshot.hashCode() result = 31 * result + feedbackSubType.contentHashCode() result = 31 * result + location.hashCode() return result @@ -57,9 +66,7 @@ class UserFeedback internal constructor( return "UserFeedback(" + "feedbackId='$feedbackId', " + "feedbackType='$feedbackType', " + - "source='$source', " + "description='$description', " + - "screenshot=$screenshot, " + "feedbackSubType=${feedbackSubType.contentToString()}, " + "location=$location" + ")" diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackSubscriber.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackSubscriber.kt new file mode 100644 index 00000000000..c312bba9f0b --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/UserFeedbackSubscriber.kt @@ -0,0 +1,8 @@ +package com.mapbox.navigation.core.internal.telemetry + +interface UserFeedbackSubscriber { + + fun registerUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) + + fun unregisterUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/event/AndroidAutoEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/event/AndroidAutoEvent.kt new file mode 100644 index 00000000000..3debeceaafb --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/telemetry/event/AndroidAutoEvent.kt @@ -0,0 +1,6 @@ +package com.mapbox.navigation.core.internal.telemetry.event + +enum class AndroidAutoEvent { + Connected, + Disconnected, +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/utils/InternalUtils.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/utils/InternalUtils.kt index 0dfff9d0e2b..ebc37f3460b 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/utils/InternalUtils.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/utils/InternalUtils.kt @@ -1,6 +1,7 @@ package com.mapbox.navigation.core.internal.utils import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigator.Navigator object InternalUtils { @@ -30,4 +31,6 @@ object InternalUtils { fun setUnconditionalPollingInterval(intervalInMillis: Long?) { UNCONDITIONAL_POLLING_INTERVAL_MILLISECONDS = intervalInMillis ?: 1000L } + + fun getNativeUserAgentFragment(): String = Navigator.getUserAgentFragment() } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/ApplicationLifecycleMonitor.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/ApplicationLifecycleMonitor.kt index 8a47ab18898..247bf8c0d5f 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/ApplicationLifecycleMonitor.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/ApplicationLifecycleMonitor.kt @@ -2,6 +2,7 @@ package com.mapbox.navigation.core.telemetry import android.app.Activity import android.app.Application +import android.content.Context import android.content.res.Configuration import android.os.Bundle import java.util.ArrayList @@ -9,12 +10,15 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference -internal class ApplicationLifecycleMonitor( - application: Application +internal class ApplicationLifecycleMonitor private constructor( + appContext: Context ) : Application.ActivityLifecycleCallbacks { companion object { private const val ONE_HUNDRED_PERCENT = 100 + + internal operator fun invoke(appContext: Context): ApplicationLifecycleMonitor = + ApplicationLifecycleMonitor(appContext) } private val startSessionTime: Long = System.currentTimeMillis() @@ -25,8 +29,8 @@ internal class ApplicationLifecycleMonitor( private val portraitTimeInMillis = AtomicReference(0.0) init { - application.registerActivityLifecycleCallbacks(this) - initCurrentOrientation(application) + (appContext as Application).registerActivityLifecycleCallbacks(this) + initCurrentOrientation(appContext) } override fun onActivityStarted(activity: Activity) { @@ -98,8 +102,8 @@ internal class ApplicationLifecycleMonitor( return (ONE_HUNDRED_PERCENT * (foregroundTime / (currentTime - startSessionTime))).toInt() } - private fun initCurrentOrientation(application: Application) { - currentOrientation.set(application.resources.configuration.orientation) + private fun initCurrentOrientation(appContext: Context) { + currentOrientation.set(appContext.resources.configuration.orientation) // If starting in portrait, set the portrait start time if (currentOrientation.get() == Configuration.ORIENTATION_PORTRAIT) { portraitStartTime.set(System.currentTimeMillis()) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/EventsMetadataInterfaceImpl.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/EventsMetadataInterfaceImpl.kt new file mode 100644 index 00000000000..688e3e377c6 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/EventsMetadataInterfaceImpl.kt @@ -0,0 +1,35 @@ +package com.mapbox.navigation.core.telemetry + +import android.content.Context +import com.mapbox.common.TelemetrySystemUtils.isPluggedIn +import com.mapbox.common.TelemetrySystemUtils.obtainApplicationState +import com.mapbox.common.TelemetrySystemUtils.obtainBatteryLevel +import com.mapbox.common.TelemetrySystemUtils.obtainCellularNetworkType +import com.mapbox.navigation.base.options.EventsAppMetadata +import com.mapbox.navigator.AppMetadata +import com.mapbox.navigator.ApplicationState +import com.mapbox.navigator.AudioType +import com.mapbox.navigator.EventsMetadata +import com.mapbox.navigator.EventsMetadataInterface + +internal class EventsMetadataInterfaceImpl( + private val appContext: Context, + private val lifecycleMonitor: ApplicationLifecycleMonitor, + eventsAppMetadata: EventsAppMetadata? +) : EventsMetadataInterface { + + private val nativeAppMetadata: AppMetadata? = eventsAppMetadata?.mapToNative() + + override fun provideEventsMetadata(): EventsMetadata = + EventsMetadata( + obtainVolumeLevel(appContext).toByte(), + obtainAudioType(appContext).mapToNativeAudioType(), + obtainScreenBrightness(appContext).toByte(), + lifecycleMonitor.obtainForegroundPercentage().toByte(), + lifecycleMonitor.obtainPortraitPercentage().toByte(), + isPluggedIn(appContext), + obtainBatteryLevel(appContext).toByte(), + obtainCellularNetworkType(appContext), + nativeAppMetadata, + ) +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/LocationsCollectorImpl.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/LocationsCollectorImpl.kt index 6e121e8eb41..50268add4ba 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/LocationsCollectorImpl.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/LocationsCollectorImpl.kt @@ -1,87 +1,87 @@ -package com.mapbox.navigation.core.telemetry - -import android.location.Location -import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry.LOG_CATEGORY -import com.mapbox.navigation.core.trip.session.LocationMatcherResult -import com.mapbox.navigation.utils.internal.logD - -internal class LocationsCollectorImpl : LocationsCollector { - - companion object { - private const val LOCATION_BUFFER_MAX_SIZE = 20 - } - - private val locationsBuffer = mutableListOf() - private val eventsLocationsBuffer = mutableListOf() - - override val lastLocation: Location? - get() = locationsBuffer.lastOrNull() - - private fun accumulatePostEventLocation(location: Location) { - val iterator = eventsLocationsBuffer.iterator() - while (iterator.hasNext()) { - iterator.next().let { - it.addPostEventLocation(location) - if (it.postEventLocationsSize() >= LOCATION_BUFFER_MAX_SIZE) { - it.onBufferFull() - iterator.remove() - } - } - } - } - - private fun accumulateLocation(location: Location) { - locationsBuffer.run { - if (size >= LOCATION_BUFFER_MAX_SIZE) { - removeAt(0) - } - add(location) - } - } - - override fun collectLocations( - locationsCollectorListener: LocationsCollector.LocationsCollectorListener - ) { - eventsLocationsBuffer.add( - EventLocations( - locationsBuffer.getCopy(), - mutableListOf(), - locationsCollectorListener - ) - ) - } - - override fun flushBuffers() { - logD("flush buffer. Pending events = ${eventsLocationsBuffer.size}", LOG_CATEGORY) - eventsLocationsBuffer.forEach { it.onBufferFull() } - eventsLocationsBuffer.clear() - } - - override fun flushBufferFor( - locationsCollectorListener: LocationsCollector.LocationsCollectorListener - ) { - logD("flush buffer for only one observer", LOG_CATEGORY) - eventsLocationsBuffer.find { - it.locationsCollectorListener === locationsCollectorListener - }?.also { - it.onBufferFull() - eventsLocationsBuffer.remove(it) - } - } - - override fun onNewRawLocation(rawLocation: Location) { - accumulateLocation(rawLocation) - accumulatePostEventLocation(rawLocation) - } - - override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) { - // Do nothing - } - - @Synchronized - private fun MutableList.getCopy(): List { - return mutableListOf().also { - it.addAll(this) - } - } -} +//package com.mapbox.navigation.core.telemetry +// +//import android.location.Location +//import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry.LOG_CATEGORY +//import com.mapbox.navigation.core.trip.session.LocationMatcherResult +//import com.mapbox.navigation.utils.internal.logD +// +//internal class LocationsCollectorImpl : LocationsCollector { +// +// companion object { +// private const val LOCATION_BUFFER_MAX_SIZE = 20 +// } +// +// private val locationsBuffer = mutableListOf() +// private val eventsLocationsBuffer = mutableListOf() +// +// override val lastLocation: Location? +// get() = locationsBuffer.lastOrNull() +// +// private fun accumulatePostEventLocation(location: Location) { +// val iterator = eventsLocationsBuffer.iterator() +// while (iterator.hasNext()) { +// iterator.next().let { +// it.addPostEventLocation(location) +// if (it.postEventLocationsSize() >= LOCATION_BUFFER_MAX_SIZE) { +// it.onBufferFull() +// iterator.remove() +// } +// } +// } +// } +// +// private fun accumulateLocation(location: Location) { +// locationsBuffer.run { +// if (size >= LOCATION_BUFFER_MAX_SIZE) { +// removeAt(0) +// } +// add(location) +// } +// } +// +// override fun collectLocations( +// locationsCollectorListener: LocationsCollector.LocationsCollectorListener +// ) { +// eventsLocationsBuffer.add( +// EventLocations( +// locationsBuffer.getCopy(), +// mutableListOf(), +// locationsCollectorListener +// ) +// ) +// } +// +// override fun flushBuffers() { +// logD("flush buffer. Pending events = ${eventsLocationsBuffer.size}", LOG_CATEGORY) +// eventsLocationsBuffer.forEach { it.onBufferFull() } +// eventsLocationsBuffer.clear() +// } +// +// override fun flushBufferFor( +// locationsCollectorListener: LocationsCollector.LocationsCollectorListener +// ) { +// logD("flush buffer for only one observer", LOG_CATEGORY) +// eventsLocationsBuffer.find { +// it.locationsCollectorListener === locationsCollectorListener +// }?.also { +// it.onBufferFull() +// eventsLocationsBuffer.remove(it) +// } +// } +// +// override fun onNewRawLocation(rawLocation: Location) { +// accumulateLocation(rawLocation) +// accumulatePostEventLocation(rawLocation) +// } +// +// override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) { +// // Do nothing +// } +// +// @Synchronized +// private fun MutableList.getCopy(): List { +// return mutableListOf().also { +// it.addAll(this) +// } +// } +//} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetry.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetry.kt deleted file mode 100644 index 33dbd8b2b4e..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetry.kt +++ /dev/null @@ -1,948 +0,0 @@ -package com.mapbox.navigation.core.telemetry - -import android.app.Application -import android.content.Context -import com.mapbox.android.core.location.LocationEngine -import com.mapbox.api.directions.v5.models.DirectionsRoute -import com.mapbox.common.TelemetrySystemUtils.generateCreateDateFormatted -import com.mapbox.common.TurnstileEvent -import com.mapbox.common.UserSKUIdentifier -import com.mapbox.geojson.Point -import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI -import com.mapbox.navigation.base.metrics.MetricEvent -import com.mapbox.navigation.base.metrics.MetricsReporter -import com.mapbox.navigation.base.options.NavigationOptions -import com.mapbox.navigation.base.route.NavigationRoute -import com.mapbox.navigation.base.trip.model.RouteLegProgress -import com.mapbox.navigation.base.trip.model.RouteProgress -import com.mapbox.navigation.base.trip.model.RouteProgressState -import com.mapbox.navigation.core.BuildConfig -import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.arrival.ArrivalObserver -import com.mapbox.navigation.core.directions.session.RoutesExtra -import com.mapbox.navigation.core.directions.session.RoutesObserver -import com.mapbox.navigation.core.internal.telemetry.UserFeedback -import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback -import com.mapbox.navigation.core.internal.telemetry.toTelemetryLocation -import com.mapbox.navigation.core.internal.telemetry.toTelemetryLocations -import com.mapbox.navigation.core.telemetry.events.AppMetadata -import com.mapbox.navigation.core.telemetry.events.FeedbackEvent -import com.mapbox.navigation.core.telemetry.events.FeedbackMetadata -import com.mapbox.navigation.core.telemetry.events.FeedbackMetadataWrapper -import com.mapbox.navigation.core.telemetry.events.FreeDriveEventType -import com.mapbox.navigation.core.telemetry.events.FreeDriveEventType.START -import com.mapbox.navigation.core.telemetry.events.FreeDriveEventType.STOP -import com.mapbox.navigation.core.telemetry.events.MetricsDirectionsRoute -import com.mapbox.navigation.core.telemetry.events.MetricsRouteProgress -import com.mapbox.navigation.core.telemetry.events.NavigationAppUserTurnstileEvent -import com.mapbox.navigation.core.telemetry.events.NavigationArriveEvent -import com.mapbox.navigation.core.telemetry.events.NavigationCancelEvent -import com.mapbox.navigation.core.telemetry.events.NavigationCustomEvent -import com.mapbox.navigation.core.telemetry.events.NavigationDepartEvent -import com.mapbox.navigation.core.telemetry.events.NavigationEvent -import com.mapbox.navigation.core.telemetry.events.NavigationFeedbackEvent -import com.mapbox.navigation.core.telemetry.events.NavigationFreeDriveEvent -import com.mapbox.navigation.core.telemetry.events.NavigationRerouteEvent -import com.mapbox.navigation.core.telemetry.events.NavigationStepData -import com.mapbox.navigation.core.telemetry.events.PhoneState -import com.mapbox.navigation.core.trip.session.NavigationSessionState -import com.mapbox.navigation.core.trip.session.NavigationSessionState.ActiveGuidance -import com.mapbox.navigation.core.trip.session.NavigationSessionState.FreeDrive -import com.mapbox.navigation.core.trip.session.NavigationSessionState.Idle -import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver -import com.mapbox.navigation.core.trip.session.RouteProgressObserver -import com.mapbox.navigation.metrics.MapboxMetricsReporter -import com.mapbox.navigation.utils.internal.Time -import com.mapbox.navigation.utils.internal.ifNonNull -import com.mapbox.navigation.utils.internal.logD -import com.mapbox.navigation.utils.internal.logW -import com.mapbox.navigation.utils.internal.toPoint -import java.util.Date -import java.util.concurrent.CopyOnWriteArraySet - -/** - * Session metadata when telemetry is on Pause. - * - * @param navigatorSessionIdentifier holds identifier between - * [MapboxNavigationTelemetry.initialize]/[MapboxNavigationTelemetry.destroy]. Allows to - * concatenate FreeDrive/ActiveGuidance/Feedback under one Telemetry session. - */ -private data class SessionMetadataOnPause( - val navigatorSessionIdentifier: String, -) - -/** - * Session metadata when Telemetry is Running - * - * @param navigatorSessionIdentifier holds identifier between - * [MapboxNavigationTelemetry.initialize]/[MapboxNavigationTelemetry.destroy]. Allows to - * concatenate FreeDrive/ActiveGuidance/Feedback under one Telemetry session. - * @param driverModeId random id of **FreeDrive** or **ActiveGuidance** mode - * @param driverModeStartTime time of start of the driver mode (**FreeDrive** or - * **ActiveGuidance**) - * @param telemetryNavSessionState telemetry is running under one of [TelemetryNavSessionState]. - * It transforms into [FeedbackEvent.DriverMode] in telemetry events. - * @param dynamicValues dynamic values for ActiveGuidance mode. - */ -private data class SessionMetadata( - val navigatorSessionIdentifier: String, - var driverModeId: String, - val driverModeStartTime: Date = Date(), - val telemetryNavSessionState: TelemetryNavSessionState, - val dynamicValues: DynamicSessionValues = DynamicSessionValues() -) - -/** - * Dynamic session values. Relevant for ActiveGuidance only. - * - * @param rerouteCount count of reroutes for particular route. - * @param timeOfReroute time of reroute. Unit is **time in millis**. - * @param timeSinceLastReroute time since last reroute. Unit is **millis**. - * @param driverModeArrivalTime arrival time of driver mode - * @param currentDistanceTraveled for the active session - */ -private data class DynamicSessionValues( - var rerouteCount: Int = 0, - var timeOfReroute: Long = 0L, - var timeSinceLastReroute: Int = 0, - var driverModeArrivalTime: Date? = null, - var currentDistanceTraveled: Int = 0, - var accumulatedDistanceTraveled: Int = 0, -) { - fun reset() { - rerouteCount = 0 - timeOfReroute = 0 - timeSinceLastReroute = 0 - driverModeArrivalTime = null - currentDistanceTraveled = 0 - accumulatedDistanceTraveled = 0 - } - - fun accumulateDistanceTraveled(distance: Int) { - accumulatedDistanceTraveled += distance - } - - fun resetCurrentDistanceTraveled() { - currentDistanceTraveled = 0 - } -} - -/** - * Telemetry nav session state. [NavigationSessionState] without [NavigationSessionState.Idle] - */ -private enum class TelemetryNavSessionState { - TRIP, - FREE_DRIVE, -} - -private const val LOG_TELEMETRY_IS_NOT_RUNNING = "Telemetry is not running" -private const val LOG_TELEMETRY_NO_ROUTE_OR_ROUTE_PROGRESS = "no route or route progress" -private const val SDK_IDENTIFIER = "mapbox-navigation-android" - -/** - * The one and only Telemetry class. This class handles all telemetry events. - * Event List: -- appUserTurnstile -- navigation.depart -- navigation.feedback -- navigation.reroute -- navigation.arrive -- navigation.cancel -The class must be initialized before any telemetry events are reported. -Attempting to use telemetry before initialization is called will throw an exception. -Initialization may be called multiple times, the call is idempotent. -The class has two public methods, postUserFeedback() and initialize(). - */ -internal object MapboxNavigationTelemetry { - internal const val LOG_CATEGORY = "MapboxNavigationTelemetry" - - private const val ONE_SECOND = 1000 - internal const val MOCK_PROVIDER = "com.mapbox.navigation.core.replay.ReplayLocationEngine" - private const val EVENT_VERSION = 7 - - private lateinit var applicationContext: Context - private lateinit var metricsReporter: MetricsReporter - private lateinit var navigationOptions: NavigationOptions - private var lifecycleMonitor: ApplicationLifecycleMonitor? = null - private var appInstance: Application? = null - set(value) { - // Don't set it multiple times to the same value, it will cause multiple registration calls. - if (field == value) { - return - } - field = value - ifNonNull(value) { app -> - logD("Lifecycle monitor created", LOG_CATEGORY) - lifecycleMonitor = ApplicationLifecycleMonitor(app) - } - } - - private var locationEngineNameExternal: String = LocationEngine::javaClass.name - private lateinit var locationsCollector: LocationsCollector - private lateinit var sdkIdentifier: String - private val feedbackEventCacheMap = LinkedHashMap() - - private var sessionState: NavigationSessionState = Idle - - private val routeData = RouteData() - - private class RouteData { - var routeProgress: RouteProgress? = null - set(value) { - field = value - onRouteDataChanged.invoke() - } - var originalRoute: NavigationRoute? = null - set(value) { - field = value - onRouteDataChanged.invoke() - } - - var needHandleDeparture = false - set(value) { - field = value - onRouteDataChanged.invoke() - } - - fun hasRouteAndRouteProgress(): Boolean { - return routeData.originalRoute != null && routeData.routeProgress != null - } - } - - private var telemetryState: NavTelemetryState = NavTelemetryState.Stopped - private val isTelemetryRunning: Boolean - get() = telemetryState is NavTelemetryState.Running - private val isTelemetryOnPause: Boolean - get() = telemetryState is NavTelemetryState.Paused - - private val routesObserver = RoutesObserver { result -> - val routes = result.navigationRoutes - val reason = result.reason - - log("onRoutesChanged. Number of routes = ${routes.size}; reason = $reason") - - when { - reason == RoutesExtra.ROUTES_UPDATE_REASON_CLEAN_UP || routes.isEmpty() -> Unit - reason == RoutesExtra.ROUTES_UPDATE_REASON_NEW -> { - log("handle a new route") - if (routeData.originalRoute != null && sessionState is ActiveGuidance) { - handleCancelNavigation() - } - resetLocalVariables() - resetDynamicValues() - routeData.originalRoute = routes.first() - routeData.needHandleDeparture = true - } - reason == RoutesExtra.ROUTES_UPDATE_REASON_ALTERNATIVE -> { - log("alternative routes received") - } - reason == RoutesExtra.ROUTES_UPDATE_REASON_REROUTE -> { - handleReroute(routes.first().directionsRoute) - } - reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH -> { - routeData.originalRoute = routes.first() - } - else -> logW( - "Unknown route update reason: [$reason]", - LOG_CATEGORY - ) - } - } - - private val arrivalObserver = object : ArrivalObserver { - override fun onNextRouteLegStart(routeLegProgress: RouteLegProgress) { - log("onNextRouteLegStart") - processArrival() - handleCancelNavigation() - resetDynamicValues() - resetRouteProgress() - routeData.needHandleDeparture = true - } - - override fun onWaypointArrival(routeProgress: RouteProgress) { - log("onWaypointDestinationArrival") - } - - override fun onFinalDestinationArrival(routeProgress: RouteProgress) { - log("onFinalDestinationArrival") - this@MapboxNavigationTelemetry.routeData.routeProgress = routeProgress - processArrival() - } - } - - private val navigationSessionStateObserver = NavigationSessionStateObserver { sessionState -> - log("session state is $sessionState") - val legacyState = this.sessionState - this.sessionState = sessionState - when (sessionState) { - is Idle, is FreeDrive -> { - if (legacyState is ActiveGuidance) { - handleCancelNavigation() - } - resetLocalVariables() - resetDynamicValues() - } - is ActiveGuidance -> Unit // do nothing - } - - when (val freeDriveEvent = getFreeDriveEvent(legacyState, this.sessionState)) { - START -> { - handleTelemetryState() - trackFreeDrive(freeDriveEvent) - } - STOP -> { - trackFreeDrive(freeDriveEvent) - handleTelemetryState() - } - null -> { - handleTelemetryState() - } - } - } - - private val routeProgressObserver = RouteProgressObserver { routeProgress -> - this.routeData.routeProgress = routeProgress - val dynamicValues = getSessionMetadataIfTelemetryRunning()?.dynamicValues - if (routeProgress.currentState == RouteProgressState.OFF_ROUTE) { - dynamicValues?.accumulateDistanceTraveled( - routeProgress.distanceTraveled.toInt() - ) - dynamicValues?.resetCurrentDistanceTraveled() - } else { - dynamicValues?.currentDistanceTraveled = - routeProgress.distanceTraveled.toInt() - } - } - - private val onRouteDataChanged: () -> Unit = { - if ( - routeData.needHandleDeparture && - routeData.hasRouteAndRouteProgress() && - isTelemetryRunning - ) { - routeData.needHandleDeparture = false - processDeparture() - } - } - - private val userFeedbackCallbacks = CopyOnWriteArraySet() - - /** - * This method must be called before using the Telemetry object - */ - fun initialize( - mapboxNavigation: MapboxNavigation, - options: NavigationOptions, - reporter: MetricsReporter, - locationsCollector: LocationsCollector = LocationsCollectorImpl(), - ) { - resetLocalVariables() - sessionState = Idle - this.locationsCollector = locationsCollector - navigationOptions = options - applicationContext = options.applicationContext - locationEngineNameExternal = options.locationEngine.javaClass.name - sdkIdentifier = SDK_IDENTIFIER - metricsReporter = reporter - feedbackEventCacheMap.clear() - postTurnstileEvent() - telemetryStart() - registerListeners(mapboxNavigation) - log("Valid initialization") - } - - fun destroy(mapboxNavigation: MapboxNavigation) { - telemetryStop() - log("MapboxMetricsReporter disable") - MapboxMetricsReporter.disable() - mapboxNavigation.run { - unregisterLocationObserver(locationsCollector) - unregisterRouteProgressObserver(routeProgressObserver) - unregisterRoutesObserver(routesObserver) - unregisterNavigationSessionStateObserver(navigationSessionStateObserver) - unregisterArrivalObserver(arrivalObserver) - } - userFeedbackCallbacks.clear() - } - - private fun telemetryStart() { - log("sessionStart") - telemetryState = when (sessionState) { - is Idle -> { - NavTelemetryState.Paused( - SessionMetadataOnPause( - navigatorSessionIdentifier = - navObtainUniversalTelemetryNavigationSessionId(), - ) - ) - } - is FreeDrive -> { - NavTelemetryState.Running( - SessionMetadata( - navigatorSessionIdentifier = - navObtainUniversalTelemetryNavigationSessionId(), - driverModeId = navObtainUniversalTelemetryNavigationModeId(), - telemetryNavSessionState = TelemetryNavSessionState.FREE_DRIVE - ) - ) - } - is ActiveGuidance -> { - NavTelemetryState.Running( - SessionMetadata( - navigatorSessionIdentifier = - navObtainUniversalTelemetryNavigationSessionId(), - driverModeId = navObtainUniversalTelemetryNavigationModeId(), - telemetryNavSessionState = TelemetryNavSessionState.TRIP - ) - ) - } - } - } - - private fun telemetryStop() { - log("sessionStop") - locationsCollector.flushBuffers() - telemetryState = NavTelemetryState.Stopped - resetLocalVariables() - } - - fun setApplicationInstance(app: Application) { - appInstance = app - } - - fun registerUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { - userFeedbackCallbacks.add(userFeedbackCallback) - } - - fun unregisterUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { - userFeedbackCallbacks.remove(userFeedbackCallback) - } - - fun postCustomEvent( - payload: String, - customEventType: String, - customEventVersion: String - ) { - createCustomEvent( - payload = payload, - customEventType = customEventType, - customEventVersion = customEventVersion, - phoneState = PhoneState.newInstance(applicationContext) - ) { - sendMetricEvent(it) - } - } - - @ExperimentalPreviewMapboxNavigationAPI - fun provideFeedbackMetadataWrapper(): FeedbackMetadataWrapper { - (telemetryState as? NavTelemetryState.Running)?.sessionMetadata?.let { sessionMetadata -> - return FeedbackMetadataWrapper( - sessionMetadata.navigatorSessionIdentifier, - sessionMetadata.driverModeId, - sessionMetadata.telemetryNavSessionState.getModeName(), - generateCreateDateFormatted(sessionMetadata.driverModeStartTime), - sessionMetadata.dynamicValues.rerouteCount, - locationsCollector.lastLocation?.toPoint(), - locationEngineNameExternal, - lifecycleMonitor?.obtainPortraitPercentage(), - lifecycleMonitor?.obtainForegroundPercentage(), - EVENT_VERSION, - PhoneState.newInstance(applicationContext), - MetricsDirectionsRoute(routeData.originalRoute), - MetricsRouteProgress(routeData.routeProgress), - createAppMetadata(), - locationsCollector - ) - } ?: throw IllegalStateException( - "Feedback Metadata might be provided when trip session is started only" - ) - } - - @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) - fun postUserFeedback( - feedbackType: String, - description: String, - @FeedbackEvent.Source feedbackSource: String, - screenshot: String?, - feedbackSubType: Array?, - feedbackMetadata: FeedbackMetadata?, - userFeedbackCallback: UserFeedbackCallback?, - ) { - createUserFeedback( - feedbackType, - description, - feedbackSource, - screenshot, - feedbackSubType, - feedbackMetadata, - userFeedbackCallback, - ) { - sendMetricEvent(it) - } - } - - @ExperimentalPreviewMapboxNavigationAPI - private fun createUserFeedback( - feedbackType: String, - description: String, - @FeedbackEvent.Source feedbackSource: String, - screenshot: String?, - feedbackSubType: Array?, - feedbackMetadata: FeedbackMetadata?, - localUserFeedbackCallback: UserFeedbackCallback?, - onEventUpdated: ((NavigationFeedbackEvent) -> Unit)?, - ) { - fun notifyUserFeedbackCallbacks(feedbackEvent: NavigationFeedbackEvent) { - val userFeedback = UserFeedback( - feedbackEvent.feedbackId, - feedbackType, - feedbackSource, - description, - screenshot, - feedbackSubType, - Point.fromLngLat(feedbackEvent.lng, feedbackEvent.lat), - ) - localUserFeedbackCallback?.onNewUserFeedback(userFeedback) - for (callback in userFeedbackCallbacks) { - callback.onNewUserFeedback(userFeedback) - } - } - if (feedbackMetadata == null) { - ifTelemetryRunning( - "User Feedback event creation failed: $LOG_TELEMETRY_IS_NOT_RUNNING" - ) { - val feedbackEvent = NavigationFeedbackEvent( - PhoneState.newInstance(applicationContext), - NavigationStepData(MetricsRouteProgress(routeData.routeProgress)) - ).apply { - this.feedbackType = feedbackType - this.source = feedbackSource - this.description = description - this.screenshot = screenshot - this.feedbackSubType = feedbackSubType - populateWithLocalVars(it) - } - notifyUserFeedbackCallbacks(feedbackEvent) - - log("collect post event locations for user feedback") - locationsCollector.collectLocations { preEventBuffer, postEventBuffer -> - log("locations ready") - feedbackEvent.apply { - locationsBefore = preEventBuffer.toTelemetryLocations() - locationsAfter = postEventBuffer.toTelemetryLocations() - } - onEventUpdated?.invoke(feedbackEvent) - } - } - } else { - log("post user feedback with feedback metadata") - val feedbackEvent = NavigationFeedbackEvent( - feedbackMetadata.phoneState, - NavigationStepData(feedbackMetadata.metricsRouteProgress), - ).apply { - this.feedbackType = feedbackType - this.source = feedbackSource - this.description = description - this.screenshot = screenshot - this.feedbackSubType = feedbackSubType - this.locationsBefore = feedbackMetadata.locationsBeforeEvent - this.locationsAfter = feedbackMetadata.locationsAfterEvent - val distanceTraveled = - getSessionMetadataIfTelemetryRunning()?.dynamicValues.retrieveDistanceTraveled() - populate( - this@MapboxNavigationTelemetry.sdkIdentifier, - feedbackMetadata.metricsDirectionsRoute, - feedbackMetadata.metricsRouteProgress, - feedbackMetadata.lastLocation, - feedbackMetadata.locationEngineNameExternal, - feedbackMetadata.percentTimeInPortrait, - feedbackMetadata.percentTimeInForeground, - feedbackMetadata.sessionIdentifier, - feedbackMetadata.driverModeIdentifier, - feedbackMetadata.driverMode, - feedbackMetadata.driverModeStartTime, - feedbackMetadata.rerouteCount, - distanceTraveled, - feedbackMetadata.eventVersion, - feedbackMetadata.appMetadata, - ) - } - notifyUserFeedbackCallbacks(feedbackEvent) - onEventUpdated?.invoke(feedbackEvent) - } - } - - private fun processDeparture() { - sendMetricEvent( - NavigationDepartEvent(PhoneState.newInstance(applicationContext)).apply { - populateWithLocalVars(getSessionMetadataIfTelemetryRunning()) - } - ) - } - - private fun getFreeDriveEvent( - oldState: NavigationSessionState, - newState: NavigationSessionState - ): FreeDriveEventType? { - return when { - oldState is FreeDrive && newState !is FreeDrive -> STOP - oldState !is FreeDrive && newState is FreeDrive -> START - else -> null - } - } - - private fun handleTelemetryState() { - val localTelemetryState = telemetryState - when (sessionState) { - is Idle -> { - if (localTelemetryState is NavTelemetryState.Running) { - telemetryState = NavTelemetryState.Paused( - SessionMetadataOnPause( - navigatorSessionIdentifier = - localTelemetryState.sessionMetadata.navigatorSessionIdentifier, - ) - ) - } - } - is FreeDrive -> { - when (localTelemetryState) { - is NavTelemetryState.Paused -> { - telemetryState = NavTelemetryState.Running( - SessionMetadata( - navigatorSessionIdentifier = localTelemetryState - .sessionMetadataOnPaused.navigatorSessionIdentifier, - driverModeId = navObtainUniversalTelemetryNavigationModeId(), - telemetryNavSessionState = TelemetryNavSessionState.FREE_DRIVE - ) - ) - } - is NavTelemetryState.Running -> { - telemetryState = NavTelemetryState.Running( - SessionMetadata( - navigatorSessionIdentifier = - localTelemetryState.sessionMetadata.navigatorSessionIdentifier, - driverModeId = navObtainUniversalTelemetryNavigationModeId(), - driverModeStartTime = Date(), - telemetryNavSessionState = TelemetryNavSessionState.FREE_DRIVE - ) - ) - } - NavTelemetryState.Stopped -> Unit // do nothing - } - } - is ActiveGuidance -> { - when (localTelemetryState) { - is NavTelemetryState.Paused -> { - telemetryState = NavTelemetryState.Running( - SessionMetadata( - navigatorSessionIdentifier = localTelemetryState - .sessionMetadataOnPaused.navigatorSessionIdentifier, - driverModeId = navObtainUniversalTelemetryNavigationModeId(), - telemetryNavSessionState = TelemetryNavSessionState.TRIP - ) - ) - } - is NavTelemetryState.Running -> { - telemetryState = NavTelemetryState.Running( - SessionMetadata( - navigatorSessionIdentifier = - localTelemetryState.sessionMetadata.navigatorSessionIdentifier, - driverModeId = navObtainUniversalTelemetryNavigationModeId(), - driverModeStartTime = Date(), - telemetryNavSessionState = TelemetryNavSessionState.TRIP - ) - ) - } - NavTelemetryState.Stopped -> Unit // do nothing - } - } - } - } - - private fun trackFreeDrive(type: FreeDriveEventType) { - log("trackFreeDrive $type") - ifTelemetryRunning( - "cannot handle free drive change: $LOG_TELEMETRY_IS_NOT_RUNNING" - ) { sessionMetadata -> - createFreeDriveEvent( - type, - sessionMetadata - ) - } - } - - private fun createFreeDriveEvent( - type: FreeDriveEventType, - sessionMetadata: SessionMetadata, - ) { - log("createFreeDriveEvent $type") - val freeDriveEvent = - NavigationFreeDriveEvent(PhoneState.newInstance(applicationContext)).apply { - populate( - type, - sessionMetadata.navigatorSessionIdentifier, - sessionMetadata.driverModeId, - sessionMetadata.driverModeStartTime - ) - } - sendEvent(freeDriveEvent) - } - - private fun createCustomEvent( - payload: String, - customEventType: String, - customEventVersion: String, - phoneState: PhoneState, - onEventUpdated: ((NavigationCustomEvent) -> Unit)? - ) { - log("customEventType: $customEventType") - val customEvent = - NavigationCustomEvent().apply { - this.payload = payload.plus(", userId = ${phoneState.userId}") - this.type = customEventType - this.driverMode = "freeDrive" - this.eventVersion = EVENT_VERSION - this.customEventVersion = customEventVersion - lat = locationsCollector.lastLocation?.latitude ?: 0.0 - lng = locationsCollector.lastLocation?.longitude ?: 0.0 - sdkIdentifier = this@MapboxNavigationTelemetry.sdkIdentifier - locationEngine = this@MapboxNavigationTelemetry.locationEngineNameExternal - } - onEventUpdated?.invoke(customEvent) - } - - private fun sendEvent(metricEvent: MetricEvent) { - log("${metricEvent::class.java} event sent") - metricsReporter.addEvent(metricEvent) - } - - private fun registerListeners(mapboxNavigation: MapboxNavigation) { - mapboxNavigation.run { - registerLocationObserver(locationsCollector) - registerRouteProgressObserver(routeProgressObserver) - registerRoutesObserver(routesObserver) - registerNavigationSessionStateObserver(navigationSessionStateObserver) - registerArrivalObserver(arrivalObserver) - } - } - - private fun sendMetricEvent(event: MetricEvent) { - if (isTelemetryRunning || isTelemetryOnPause) { - sendEvent(event) - } else { - log( - "${event::class.java} is not sent. Caused by: " + - "Telemetry Session started: $isTelemetryRunning." - ) - } - } - - private fun handleReroute(route: DirectionsRoute) { - if (!routeData.hasRouteAndRouteProgress()) { - log("cannot handle reroute: $LOG_TELEMETRY_NO_ROUTE_OR_ROUTE_PROGRESS") - return - } - ifTelemetryRunning( - "cannot handle reroute: $LOG_TELEMETRY_IS_NOT_RUNNING" - ) { sessionMetadata -> - log("handleReroute") - - sessionMetadata.dynamicValues.run { - val currentTime = Time.SystemImpl.millis() - timeSinceLastReroute = (currentTime - timeOfReroute).toInt() - timeOfReroute = currentTime - rerouteCount++ - } - - val navigationRerouteEvent = NavigationRerouteEvent( - PhoneState.newInstance(applicationContext), - NavigationStepData(MetricsRouteProgress(routeData.routeProgress)), - ).apply { - secondsSinceLastReroute = - sessionMetadata - .dynamicValues - .timeSinceLastReroute / ONE_SECOND - - newDistanceRemaining = route.distance().toInt() - newDurationRemaining = route.duration().toInt() - newGeometry = obtainGeometry(route) - populateWithLocalVars(sessionMetadata) - } - - locationsCollector.collectLocations { preEventBuffer, postEventBuffer -> - navigationRerouteEvent.apply { - locationsBefore = preEventBuffer.toTelemetryLocations() - locationsAfter = postEventBuffer.toTelemetryLocations() - } - - sendMetricEvent(navigationRerouteEvent) - } - } - } - - private fun handleCancelNavigation() { - log("handleSessionCanceled") - locationsCollector.flushBuffers() - - val cancelEvent = - NavigationCancelEvent(PhoneState.newInstance(applicationContext)).apply { - populateWithLocalVars(getSessionMetadataIfTelemetryRunning()) - } - cancelEvent.arrivalTimestamp = generateCreateDateFormatted(Date()) - sendMetricEvent(cancelEvent) - } - - private fun postTurnstileEvent() { - val turnstileEvent = TurnstileEvent( - UserSKUIdentifier.NAV2_SES_MAU, - sdkIdentifier, - BuildConfig.MAPBOX_NAVIGATION_VERSION_NAME, - ) - val event = NavigationAppUserTurnstileEvent(turnstileEvent) - log("TurnstileEvent sent") - metricsReporter.sendTurnstileEvent(event.event) - } - - private fun processArrival() { - if (!routeData.hasRouteAndRouteProgress()) { - log("cannot handle process arrival: $LOG_TELEMETRY_NO_ROUTE_OR_ROUTE_PROGRESS") - return - } - ifTelemetryRunning( - "cannot handle process arrival: $LOG_TELEMETRY_IS_NOT_RUNNING" - ) { sessionMetadata -> - log("you have arrived") - sessionMetadata.dynamicValues.driverModeArrivalTime = Date() - - val arriveEvent = - NavigationArriveEvent(PhoneState.newInstance(applicationContext)).apply { - populateWithLocalVars(sessionMetadata) - } - sendMetricEvent(arriveEvent) - } - } - - private fun NavigationEvent.populateWithLocalVars(sessionMetadata: SessionMetadata?) { - val distanceTraveled = sessionMetadata?.dynamicValues.retrieveDistanceTraveled() - this.populate( - this@MapboxNavigationTelemetry.sdkIdentifier, - MetricsDirectionsRoute(routeData.originalRoute), - MetricsRouteProgress(routeData.routeProgress), - locationsCollector.lastLocation?.toPoint(), - locationEngineNameExternal, - lifecycleMonitor?.obtainPortraitPercentage(), - lifecycleMonitor?.obtainForegroundPercentage(), - sessionMetadata?.navigatorSessionIdentifier, - sessionMetadata?.driverModeId, - sessionMetadata?.telemetryNavSessionState?.getModeName(), - sessionMetadata?.driverModeStartTime?.let { generateCreateDateFormatted(it) }, - sessionMetadata?.dynamicValues?.rerouteCount, - distanceTraveled, - EVENT_VERSION, - createAppMetadata() - ) - } - - private fun DynamicSessionValues?.retrieveDistanceTraveled(): Int { - val currentDistanceTraveled = this?.currentDistanceTraveled ?: 0 - val accumulatedDistanceTraveled = this?.accumulatedDistanceTraveled ?: 0 - return currentDistanceTraveled + accumulatedDistanceTraveled - } - - private fun NavigationFreeDriveEvent.populate( - type: FreeDriveEventType, - navSessionIdentifier: String, - modeId: String, - modeStartTime: Date - ) { - log("populateFreeDriveEvent") - - this.apply { - eventType = type.type - location = locationsCollector.lastLocation?.toTelemetryLocation() - eventVersion = EVENT_VERSION - locationEngine = locationEngineNameExternal - percentTimeInPortrait = lifecycleMonitor?.obtainPortraitPercentage() ?: 100 - percentTimeInForeground = lifecycleMonitor?.obtainForegroundPercentage() ?: 100 - simulation = locationEngineNameExternal == MOCK_PROVIDER - navigatorSessionIdentifier = navSessionIdentifier - sessionIdentifier = modeId - startTimestamp = generateCreateDateFormatted(modeStartTime) - appMetadata = createAppMetadata() - } - } - - private fun resetDynamicValues() { - getSessionMetadataIfTelemetryRunning()?.dynamicValues?.reset() - } - - private fun createAppMetadata(): AppMetadata? { - navigationOptions.eventsAppMetadata?.let { - return AppMetadata(it.name, it.version, it.userId, it.sessionId) - } ?: return null - } - - private fun resetLocalVariables() { - resetOriginalRoute() - resetRouteProgress() - routeData.needHandleDeparture = false - } - - private fun resetRouteProgress() { - log("resetRouteProgress") - routeData.routeProgress = null - } - - private fun resetOriginalRoute() { - log("resetOriginalRoute") - routeData.originalRoute = null - } - - private fun getSessionMetadataIfTelemetryRunning(): SessionMetadata? = - (telemetryState as? NavTelemetryState.Running)?.sessionMetadata - - private fun ifTelemetryRunning( - elseLog: String? = null, - func: ((SessionMetadata) -> Unit) - ) { - when (val telemetryState = telemetryState) { - is NavTelemetryState.Running -> { - func(telemetryState.sessionMetadata) - } - is NavTelemetryState.Paused -> { - elseLog?.let { log("Telemetry Paused; $it") } - } - NavTelemetryState.Stopped -> { - elseLog?.let { log("Telemetry Stopped; $it") } - } - } - } - - @FeedbackEvent.DriverMode - private fun TelemetryNavSessionState.getModeName() = - when (this) { - TelemetryNavSessionState.TRIP -> FeedbackEvent.DRIVER_MODE_TRIP - TelemetryNavSessionState.FREE_DRIVE -> FeedbackEvent.DRIVER_MODE_FREE_DRIVE - } - - private fun log(message: String) { - logD(message, LOG_CATEGORY) - } - - private sealed class NavTelemetryState { - /** - * Running - */ - class Running(val sessionMetadata: SessionMetadata) : NavTelemetryState() - - /** - * Paused when navigator is in [NavigationSessionState.Idle] - */ - class Paused(val sessionMetadataOnPaused: SessionMetadataOnPause) : NavTelemetryState() - - /** - * Stopped - */ - object Stopped : NavTelemetryState() - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavEventsPopulateUtil.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavEventsPopulateUtil.kt index 0735734c18d..91b22de32d6 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavEventsPopulateUtil.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavEventsPopulateUtil.kt @@ -1,94 +1,94 @@ -package com.mapbox.navigation.core.telemetry - -import com.mapbox.geojson.Point -import com.mapbox.navigation.core.telemetry.events.AppMetadata -import com.mapbox.navigation.core.telemetry.events.FeedbackEvent -import com.mapbox.navigation.core.telemetry.events.MetricsDirectionsRoute -import com.mapbox.navigation.core.telemetry.events.MetricsRouteProgress -import com.mapbox.navigation.core.telemetry.events.NavigationEvent -import com.mapbox.navigation.utils.internal.logD - -/** - * Populate base Navigation Event - * - * @param sdkIdentifier sdk identifier - * @param originalRoute original route (optional) - * @param routeProgress route progress (optional) - * @param lastLocation last location - * @param locationEngineNameExternal location engine name - * @param percentTimeInPortrait [ApplicationLifecycleMonitor.obtainPortraitPercentage] - * @param percentTimeInForeground [ApplicationLifecycleMonitor.obtainForegroundPercentage] - * @param navigatorSessionIdentifier nav session id (identifier of instance of MapboxNavigation) - * @param driverModeId id of driver mode (FreeDrive or ActiveGuidance) - * @param driverMode one of [FeedbackEvent.DriverMode] - * @param driverModeStartTime driver mode start time. - * Use [TelemetryUtils.generateCreateDateFormatted] - * @param rerouteCount reroute count - * @param distanceTraveled accumulated for the session - * @param eventVersion events version [MapboxNavigationTelemetry.EVENT_VERSION] - * @param appMetadata use [MapboxNavigationTelemetry.createAppMetadata] - */ -internal fun NavigationEvent.populate( - sdkIdentifier: String, - originalRoute: MetricsDirectionsRoute, - routeProgress: MetricsRouteProgress, - lastLocation: Point?, - locationEngineNameExternal: String?, - percentTimeInPortrait: Int?, - percentTimeInForeground: Int?, - navigatorSessionIdentifier: String?, - driverModeId: String?, - @FeedbackEvent.DriverMode driverMode: String?, - driverModeStartTime: String?, - rerouteCount: Int?, - distanceTraveled: Int, - eventVersion: Int, - appMetadata: AppMetadata?, -) { - logD("populateNavigationEvent", "MapboxNavigationTelemetry") - - this.sdkIdentifier = sdkIdentifier - - stepIndex = routeProgress.stepIndex - - distanceRemaining = routeProgress.distanceRemaining - durationRemaining = routeProgress.durationRemaining - distanceCompleted = distanceTraveled - - geometry = routeProgress.directionsRouteGeometry - profile = routeProgress.directionsRouteProfile - requestIdentifier = routeProgress.directionsRouteRequestIdentifier - stepCount = routeProgress.directionsRouteStepCount - legIndex = routeProgress.directionsRouteIndex - legCount = routeProgress.legCount - - absoluteDistanceToDestination = - obtainAbsoluteDistance(lastLocation, routeProgress.directionsRouteDestination) - estimatedDistance = routeProgress.directionsRouteDistance - estimatedDuration = routeProgress.directionsRouteDuration - totalStepCount = routeProgress.directionsRouteStepCount - - originalStepCount = originalRoute.stepCount - originalEstimatedDistance = originalRoute.distance - originalEstimatedDuration = originalRoute.duration - originalRequestIdentifier = originalRoute.requestIdentifier - originalGeometry = originalRoute.geometry - - locationEngine = locationEngineNameExternal - tripIdentifier = navObtainUniversalTelemetryTripId() - lat = lastLocation?.latitude() ?: 0.0 - lng = lastLocation?.longitude() ?: 0.0 - this.simulation = locationEngineNameExternal == MapboxNavigationTelemetry.MOCK_PROVIDER - this.percentTimeInPortrait = percentTimeInPortrait ?: 100 - this.percentTimeInForeground = percentTimeInForeground ?: 100 - - this.navigatorSessionIdentifier = navigatorSessionIdentifier - - this.sessionIdentifier = driverModeId - this.startTimestamp = driverModeStartTime - this.driverMode = driverMode - rerouteCount?.let { this.rerouteCount = it } - - this.eventVersion = eventVersion - this.appMetadata = appMetadata -} +//package com.mapbox.navigation.core.telemetry +// +//import com.mapbox.geojson.Point +//import com.mapbox.navigation.core.telemetry.events.AppMetadata +//import com.mapbox.navigation.core.telemetry.events.FeedbackEvent +//import com.mapbox.navigation.core.telemetry.events.MetricsDirectionsRoute +//import com.mapbox.navigation.core.telemetry.events.MetricsRouteProgress +//import com.mapbox.navigation.core.telemetry.events.NavigationEvent +//import com.mapbox.navigation.utils.internal.logD +// +///** +// * Populate base Navigation Event +// * +// * @param sdkIdentifier sdk identifier +// * @param originalRoute original route (optional) +// * @param routeProgress route progress (optional) +// * @param lastLocation last location +// * @param locationEngineNameExternal location engine name +// * @param percentTimeInPortrait [ApplicationLifecycleMonitor.obtainPortraitPercentage] +// * @param percentTimeInForeground [ApplicationLifecycleMonitor.obtainForegroundPercentage] +// * @param navigatorSessionIdentifier nav session id (identifier of instance of MapboxNavigation) +// * @param driverModeId id of driver mode (FreeDrive or ActiveGuidance) +// * @param driverMode one of [FeedbackEvent.DriverMode] +// * @param driverModeStartTime driver mode start time. +// * Use [TelemetryUtils.generateCreateDateFormatted] +// * @param rerouteCount reroute count +// * @param distanceTraveled accumulated for the session +// * @param eventVersion events version [MapboxNavigationTelemetry.EVENT_VERSION] +// * @param appMetadata use [MapboxNavigationTelemetry.createAppMetadata] +// */ +//internal fun NavigationEvent.populate( +// sdkIdentifier: String, +// originalRoute: MetricsDirectionsRoute, +// routeProgress: MetricsRouteProgress, +// lastLocation: Point?, +// locationEngineNameExternal: String?, +// percentTimeInPortrait: Int?, +// percentTimeInForeground: Int?, +// navigatorSessionIdentifier: String?, +// driverModeId: String?, +// @FeedbackEvent.DriverMode driverMode: String?, +// driverModeStartTime: String?, +// rerouteCount: Int?, +// distanceTraveled: Int, +// eventVersion: Int, +// appMetadata: AppMetadata?, +//) { +// logD("populateNavigationEvent", "MapboxNavigationTelemetry") +// +// this.sdkIdentifier = sdkIdentifier +// +// stepIndex = routeProgress.stepIndex +// +// distanceRemaining = routeProgress.distanceRemaining +// durationRemaining = routeProgress.durationRemaining +// distanceCompleted = distanceTraveled +// +// geometry = routeProgress.directionsRouteGeometry +// profile = routeProgress.directionsRouteProfile +// requestIdentifier = routeProgress.directionsRouteRequestIdentifier +// stepCount = routeProgress.directionsRouteStepCount +// legIndex = routeProgress.directionsRouteIndex +// legCount = routeProgress.legCount +// +// absoluteDistanceToDestination = +// obtainAbsoluteDistance(lastLocation, routeProgress.directionsRouteDestination) +// estimatedDistance = routeProgress.directionsRouteDistance +// estimatedDuration = routeProgress.directionsRouteDuration +// totalStepCount = routeProgress.directionsRouteStepCount +// +// originalStepCount = originalRoute.stepCount +// originalEstimatedDistance = originalRoute.distance +// originalEstimatedDuration = originalRoute.duration +// originalRequestIdentifier = originalRoute.requestIdentifier +// originalGeometry = originalRoute.geometry +// +// locationEngine = locationEngineNameExternal +// tripIdentifier = navObtainUniversalTelemetryTripId() +// lat = lastLocation?.latitude() ?: 0.0 +// lng = lastLocation?.longitude() ?: 0.0 +// this.simulation = locationEngineNameExternal == MapboxNavigationTelemetry.MOCK_PROVIDER +// this.percentTimeInPortrait = percentTimeInPortrait ?: 100 +// this.percentTimeInForeground = percentTimeInForeground ?: 100 +// +// this.navigatorSessionIdentifier = navigatorSessionIdentifier +// +// this.sessionIdentifier = driverModeId +// this.startTimestamp = driverModeStartTime +// this.driverMode = driverMode +// rerouteCount?.let { this.rerouteCount = it } +// +// this.eventVersion = eventVersion +// this.appMetadata = appMetadata +//} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavTelemetryUtils.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavTelemetryUtils.kt index 8ce0280378b..68d0aeb2dd6 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavTelemetryUtils.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavTelemetryUtils.kt @@ -10,9 +10,15 @@ import com.mapbox.common.TelemetrySystemUtils import com.mapbox.core.constants.Constants import com.mapbox.geojson.Point import com.mapbox.geojson.utils.PolylineUtils +import com.mapbox.navigation.base.options.EventsAppMetadata import com.mapbox.navigation.base.utils.DecodeUtils.completeGeometryToPoints +import com.mapbox.navigation.core.internal.telemetry.event.AndroidAutoEvent import com.mapbox.navigation.core.telemetry.audio.AudioTypeChain +import com.mapbox.navigation.core.telemetry.audio.AudioTypeResolver import com.mapbox.navigation.utils.internal.ifNonNull +import com.mapbox.navigator.AppMetadata +import com.mapbox.navigator.AudioType +import com.mapbox.navigator.OuterDeviceAction import com.mapbox.turf.TurfConstants import com.mapbox.turf.TurfMeasurement import kotlin.math.floor @@ -112,7 +118,7 @@ internal fun obtainScreenBrightness(context: Context): Int = * Provide audio type * @see [com.mapbox.navigation.core.telemetry.audio.AudioTypeResolver] */ -internal fun obtainAudioType(context: Context): String = +internal fun obtainAudioType(context: Context): AudioTypeResolver = AudioTypeChain().setup().obtainAudioType(context) private fun calculateScreenBrightnessPercentage(screenBrightness: Int): Int = @@ -129,3 +135,20 @@ internal fun navObtainUniversalTelemetryNavigationModeId(): String = internal fun navObtainUniversalTelemetryTripId(): String = TelemetrySystemUtils.obtainUniversalUniqueIdentifier() + +internal fun AndroidAutoEvent.mapToNative(): OuterDeviceAction = + when (this) { + AndroidAutoEvent.Connected -> OuterDeviceAction.CONNECTED + AndroidAutoEvent.Disconnected -> OuterDeviceAction.DISCONNECTED + } + +internal fun AudioTypeResolver.mapToNativeAudioType(): AudioType = + when (this) { + is AudioTypeResolver.Bluetooth -> AudioType.BLUETOOTH + is AudioTypeResolver.Headphones -> AudioType.HEADPHONES + is AudioTypeResolver.Speaker -> AudioType.SPEAKER + is AudioTypeResolver.Unknown -> AudioType.UNKNOWN + } + +internal fun EventsAppMetadata.mapToNative(): AppMetadata = + AppMetadata(this.name, this.version, this.userId, this.sessionId) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavigationTelemetry.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavigationTelemetry.kt new file mode 100644 index 00000000000..e95f2d1fc3a --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/NavigationTelemetry.kt @@ -0,0 +1,87 @@ +package com.mapbox.navigation.core.telemetry + +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.internal.telemetry.TelemetryAndroidAutoInterface +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackInternal.Companion.toInternal +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackSubscriber +import com.mapbox.navigation.core.internal.telemetry.event.AndroidAutoEvent +import com.mapbox.navigation.core.telemetry.events.FeedbackMetadataWrapper +import com.mapbox.navigation.core.telemetry.events.UserFeedback.Companion.toNative +import com.mapbox.navigation.core.trip.session.TripSession +import com.mapbox.navigation.core.trip.session.TripSessionState +import com.mapbox.navigator.Telemetry +import java.util.concurrent.CopyOnWriteArraySet + +internal class NavigationTelemetry private constructor( + private val tripSession: TripSession, + private val nativeTelemetry: () -> Telemetry, +) : TelemetryAndroidAutoInterface, UserFeedbackSubscriber { + + private val userFeedbackCallbacks = CopyOnWriteArraySet() + + internal companion object { + operator fun invoke( + tripSession: TripSession, + nativeTelemetry: () -> Telemetry + ): NavigationTelemetry = + NavigationTelemetry(tripSession, nativeTelemetry) + } + + @ExperimentalPreviewMapboxNavigationAPI + fun provideFeedbackMetadataWrapper(): FeedbackMetadataWrapper = + when (tripSession.getState()) { + TripSessionState.STARTED -> + FeedbackMetadataWrapper(nativeTelemetry().startBuildUserFeedbackMetadata()) + + TripSessionState.STOPPED -> throw IllegalStateException( + "Feedback Metadata might be provided when trip session is started only" + ) + } + + @ExperimentalPreviewMapboxNavigationAPI + fun postUserFeedback( + userFeedback: com.mapbox.navigation.core.telemetry.events.UserFeedback, + userFeedbackCallback: UserFeedbackCallback?, + ) { + fun notifyUserFeedbackCallbacks(location: Point) { + val userFeedbackInternal = userFeedback.toInternal( + "-1", + location, + ) + userFeedbackCallback?.onNewUserFeedback(userFeedbackInternal) + for (callback in userFeedbackCallbacks) { + callback.onNewUserFeedback(userFeedbackInternal) + } + } + + nativeTelemetry().postUserFeedback( + userFeedback.feedbackMetadata?.userFeedbackMetadata + ?: nativeTelemetry().startBuildUserFeedbackMetadata().metadata, + userFeedback.toNative(), + ) { result -> + result.fold({ + // do nothing + }, { + notifyUserFeedbackCallbacks(it) + }) + } + } + + fun postCustomEvent(type: String, version: String, payload: String?) { + nativeTelemetry().postTelemetryCustomEvent(type, version, payload) + } + + override fun postAndroidAutoEvent(event: AndroidAutoEvent) { + nativeTelemetry().postOuterDeviceEvent(event.mapToNative()) + } + + override fun registerUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { + userFeedbackCallbacks.add(userFeedbackCallback) + } + + override fun unregisterUserFeedbackCallback(userFeedbackCallback: UserFeedbackCallback) { + userFeedbackCallbacks.remove(userFeedbackCallback) + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/audio/AudioTypeResolver.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/audio/AudioTypeResolver.kt index bba02825ff1..d368550c922 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/audio/AudioTypeResolver.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/audio/AudioTypeResolver.kt @@ -6,27 +6,20 @@ import android.os.Build internal sealed class AudioTypeResolver { - private companion object { - private const val BLUETOOTH = "bluetooth" - private const val HEADPHONES = "headphones" - private const val SPEAKER = "speaker" - private const val UNKNOWN = "unknown" - } - internal lateinit var chain: AudioTypeResolver open fun nextChain(chain: AudioTypeResolver) { this.chain = chain } - abstract fun obtainAudioType(context: Context): String + abstract fun obtainAudioType(context: Context): AudioTypeResolver class Bluetooth : AudioTypeResolver() { - override fun obtainAudioType(context: Context): String { + override fun obtainAudioType(context: Context): AudioTypeResolver { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager? - ?: return UNKNOWN + ?: return Unknown() return if (audioManager.isBluetoothScoOn) { - BLUETOOTH + this } else { chain.obtainAudioType(context) } @@ -34,9 +27,9 @@ internal sealed class AudioTypeResolver { } class Headphones : AudioTypeResolver() { - override fun obtainAudioType(context: Context): String { + override fun obtainAudioType(context: Context): AudioTypeResolver { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager? - ?: return UNKNOWN + ?: return Unknown() val isHeadphonesOn = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) devices.isNotEmpty() @@ -44,7 +37,7 @@ internal sealed class AudioTypeResolver { audioManager.isWiredHeadsetOn } return if (isHeadphonesOn) { - HEADPHONES + this } else { chain.obtainAudioType(context) } @@ -52,11 +45,11 @@ internal sealed class AudioTypeResolver { } class Speaker : AudioTypeResolver() { - override fun obtainAudioType(context: Context): String { + override fun obtainAudioType(context: Context): AudioTypeResolver { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager? - ?: return UNKNOWN + ?: return Unknown() return if (audioManager.isSpeakerphoneOn) { - SPEAKER + this } else { chain.obtainAudioType(context) } @@ -67,6 +60,6 @@ internal sealed class AudioTypeResolver { override fun nextChain(chain: AudioTypeResolver) { } - override fun obtainAudioType(context: Context): String = UNKNOWN + override fun obtainAudioType(context: Context): AudioTypeResolver = this } } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/CoreTelemetryEventUtils.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/CoreTelemetryEventUtils.kt deleted file mode 100644 index e257733c632..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/CoreTelemetryEventUtils.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value - -internal fun String.toValue(): Value = Value.valueOf(this) -internal fun Boolean.toValue(): Value = Value.valueOf(this) -internal fun Int.toValue(): Value = Value.valueOf(this.toLong()) -internal fun Double.toValue(): Value = Value.valueOf(this) - -// FIXME: require to support Value.valueOf(Float). #CORESDK-1351 -internal fun Float.toValue(): Value = Value.valueOf(this.toDouble()) - -internal fun TelemetryLocation.toValue(): Value { - val fields = hashMapOf() - fields["lat"] = latitude.toValue() - fields["lng"] = longitude.toValue() - fields["speed"] = speed.toValue() - fields["course"] = bearing.toValue() - fields["altitude"] = altitude.toValue() - fields["timestamp"] = timestamp.toValue() - fields["horizontalAccuracy"] = horizontalAccuracy.toValue() - fields["verticalAccuracy"] = verticalAccuracy.toValue() - return Value.valueOf(fields) -} - -internal fun AppMetadata.toValue(): Value { - val fields = hashMapOf() - fields["name"] = name.toValue() - fields["version"] = version.toValue() - userId?.let { fields["userId"] = it.toValue() } - sessionId?.let { fields["sessionId"] = it.toValue() } - return Value.valueOf(fields) -} - -internal fun NavigationStepData.toValue(): Value { - val fields = hashMapOf() - - upcomingInstruction?.let { fields["upcomingInstruction"] = it.toValue() } - upcomingModifier?.let { fields["upcomingModifier"] = it.toValue() } - upcomingName?.let { fields["upcomingName"] = it.toValue() } - upcomingType?.let { fields["upcomingType"] = it.toValue() } - previousInstruction?.let { fields["previousInstruction"] = it.toValue() } - previousModifier?.let { fields["previousModifier"] = it.toValue() } - fields["previousName"] = previousName.toValue() - previousType?.let { fields["previousType"] = it.toValue() } - fields["distance"] = distance.toValue() - fields["duration"] = duration.toValue() - fields["distanceRemaining"] = distanceRemaining.toValue() - fields["durationRemaining"] = durationRemaining.toValue() - - return Value.valueOf(fields) -} - -internal fun Array.toValue(toValue: T.() -> Value): Value { - val values = mutableListOf() - for (item in this) { - values.add(item.toValue()) - } - return Value.valueOf(values) -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadata.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadata.kt index e42c3d95232..7b8fe36ef8e 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadata.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadata.kt @@ -1,12 +1,11 @@ package com.mapbox.navigation.core.telemetry.events -import android.location.Location import com.google.gson.Gson -import com.mapbox.geojson.Point import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.internal.telemetry.toTelemetryLocations -import com.mapbox.navigation.core.telemetry.LocationsCollector +import com.mapbox.navigation.utils.internal.logE +import com.mapbox.navigator.UserFeedbackHandle +import com.mapbox.navigator.UserFeedbackMetadata /** * It's the wrapper of [FeedbackMetadata] that collect locations after instance of @@ -20,65 +19,12 @@ import com.mapbox.navigation.core.telemetry.LocationsCollector */ @ExperimentalPreviewMapboxNavigationAPI class FeedbackMetadataWrapper internal constructor( - private val sessionIdentifier: String, - private val driverModeIdentifier: String, - @FeedbackEvent.DriverMode private val driverMode: String, - private val driverModeStartTime: String, - private val rerouteCount: Int, - private val lastLocation: Point?, - private val locationEngineNameExternal: String, - private val percentTimeInPortrait: Int?, - private val percentTimeInForeground: Int?, - private val eventVersion: Int, - private val phoneState: PhoneState, - private val metricsDirectionsRoute: MetricsDirectionsRoute, - private val metricsRouteProgress: MetricsRouteProgress, - private val appMetadata: AppMetadata?, - private val locationsCollector: LocationsCollector, + private val userFeedbackHandle: UserFeedbackHandle ) { - private var bufferFlushed = false - - private val locationsBefore = mutableListOf() - private val locationsAfter = mutableListOf() - - private val locationsBufferListener = - LocationsCollector.LocationsCollectorListener { preEventLocations, postEventLocations -> - locationsBefore.addAll(preEventLocations) - locationsAfter.addAll(postEventLocations) - bufferFlushed = true - } - - init { - locationsCollector.collectLocations(locationsBufferListener) - } - /** - * Provide [FeedbackMetadata] with locations that were collected after creating instance of - * [FeedbackMetadataWrapper]. + * Provide the instance of [FeedbackMetadata] */ - fun get(): FeedbackMetadata { - if (!bufferFlushed) { - locationsCollector.flushBufferFor(locationsBufferListener) - } - return FeedbackMetadata( - sessionIdentifier, - driverModeIdentifier, - driverMode, - driverModeStartTime, - rerouteCount, - locationsBefore.toTelemetryLocations(), - locationsAfter.toTelemetryLocations(), - lastLocation, - locationEngineNameExternal, - percentTimeInPortrait, - percentTimeInForeground, - eventVersion, - phoneState, - metricsDirectionsRoute, - metricsRouteProgress, - appMetadata, - ) - } + fun get(): FeedbackMetadata = FeedbackMetadata(userFeedbackHandle.metadata) } /** @@ -91,39 +37,30 @@ class FeedbackMetadataWrapper internal constructor( */ @ExperimentalPreviewMapboxNavigationAPI class FeedbackMetadata internal constructor( - internal val sessionIdentifier: String? = null, - internal val driverModeIdentifier: String? = null, - @FeedbackEvent.DriverMode internal val driverMode: String? = null, - internal val driverModeStartTime: String? = null, - internal val rerouteCount: Int = 0, - internal val locationsBeforeEvent: Array? = null, - internal val locationsAfterEvent: Array? = null, - internal val lastLocation: Point? = null, - internal val locationEngineNameExternal: String? = null, - internal val percentTimeInPortrait: Int? = null, - internal val percentTimeInForeground: Int? = null, - internal val eventVersion: Int, - internal val phoneState: PhoneState, - internal val metricsDirectionsRoute: MetricsDirectionsRoute, - internal val metricsRouteProgress: MetricsRouteProgress, - internal val appMetadata: AppMetadata? = null, + internal val userFeedbackMetadata: UserFeedbackMetadata, ) { companion object { + private const val LOG_CATEGORY = "FeedbackMetadata" + /** * Create a new instance of [FeedbackMetadata] from json. */ @JvmStatic @ExperimentalPreviewMapboxNavigationAPI fun fromJson(json: String): FeedbackMetadata? = - Gson().fromJson(json, FeedbackMetadata::class.java) + try { + Gson().fromJson(json, FeedbackMetadata::class.java) + } catch (e: Exception) { + logE("from json exception: $e", LOG_CATEGORY) + null + } } /** * Serialize [FeedbackMetadata] to json string. */ - fun toJson(gson: Gson): String = - gson.toJson(this) + fun toJson(gson: Gson): String = gson.toJson(this) /** * Regenerate whenever a change is made @@ -134,22 +71,7 @@ class FeedbackMetadata internal constructor( other as FeedbackMetadata - if (sessionIdentifier != other.sessionIdentifier) return false - if (driverModeIdentifier != other.driverModeIdentifier) return false - if (driverMode != other.driverMode) return false - if (driverModeStartTime != other.driverModeStartTime) return false - if (rerouteCount != other.rerouteCount) return false - if (!locationsBeforeEvent.contentEquals(other.locationsBeforeEvent)) return false - if (!locationsAfterEvent.contentEquals(other.locationsAfterEvent)) return false - if (lastLocation != other.lastLocation) return false - if (locationEngineNameExternal != other.locationEngineNameExternal) return false - if (percentTimeInPortrait != other.percentTimeInPortrait) return false - if (percentTimeInForeground != other.percentTimeInForeground) return false - if (eventVersion != other.eventVersion) return false - if (phoneState != other.phoneState) return false - if (metricsDirectionsRoute != other.metricsDirectionsRoute) return false - if (metricsRouteProgress != other.metricsRouteProgress) return false - if (appMetadata != other.appMetadata) return false + if (userFeedbackMetadata != other.userFeedbackMetadata) return false return true } @@ -158,45 +80,6 @@ class FeedbackMetadata internal constructor( * Regenerate whenever a change is made */ override fun hashCode(): Int { - var result = sessionIdentifier.hashCode() - result = 31 * result + driverModeIdentifier.hashCode() - result = 31 * result + driverMode.hashCode() - result = 31 * result + driverModeStartTime.hashCode() - result = 31 * result + rerouteCount.hashCode() - result = 31 * result + locationsBeforeEvent.hashCode() - result = 31 * result + locationsAfterEvent.hashCode() - result = 31 * result + lastLocation.hashCode() - result = 31 * result + locationEngineNameExternal.hashCode() - result = 31 * result + percentTimeInPortrait.hashCode() - result = 31 * result + percentTimeInForeground.hashCode() - result = 31 * result + eventVersion.hashCode() - result = 31 * result + phoneState.hashCode() - result = 31 * result + metricsDirectionsRoute.hashCode() - result = 31 * result + metricsRouteProgress.hashCode() - result = 31 * result + appMetadata.hashCode() - return result + return userFeedbackMetadata.hashCode() } - - /** - * Regenerate whenever a change is made - */ - override fun toString(): String = - "FeedbackMetadata(" + - "sessionIdentifier='$sessionIdentifier', " + - "driverModeIdentifier=$driverModeIdentifier, " + - "driverMode=$driverMode, " + - "driverModeStartTime=$driverModeStartTime, " + - "rerouteCount=$rerouteCount, " + - "locationsBeforeEvent=$locationsBeforeEvent, " + - "locationsAfterEvent=$locationsAfterEvent, " + - "lastLocation=$lastLocation, " + - "locationEngineNameExternal=$locationEngineNameExternal, " + - "percentTimeInPortrait=$percentTimeInPortrait, " + - "percentTimeInForeground=$percentTimeInForeground, " + - "eventVersion=$eventVersion, " + - "phoneState=$phoneState, " + - "metricsDirectionsRoute=$metricsDirectionsRoute, " + - "metricsRouteProgress=$metricsRouteProgress, " + - "appMetadata=$appMetadata" + - ")" } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationAppUserTurnstileEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationAppUserTurnstileEvent.kt deleted file mode 100644 index 4389e23e721..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationAppUserTurnstileEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.common.TurnstileEvent - -internal class NavigationAppUserTurnstileEvent( - internal val event: TurnstileEvent -) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationArriveEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationArriveEvent.kt deleted file mode 100644 index f44110353f3..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationArriveEvent.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import com.mapbox.bindgen.Value -import com.mapbox.navigation.base.metrics.NavigationMetrics - -@SuppressLint("ParcelCreator") -internal class NavigationArriveEvent( - phoneState: PhoneState -) : NavigationEvent(phoneState) { - - override fun getEventName(): String = NavigationMetrics.ARRIVE - - override fun customFields(): Map? = null -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationCancelEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationCancelEvent.kt deleted file mode 100644 index 23ee3f9a50d..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationCancelEvent.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import com.mapbox.bindgen.Value -import com.mapbox.navigation.base.metrics.NavigationMetrics - -@SuppressLint("ParcelCreator") -internal class NavigationCancelEvent( - phoneState: PhoneState -) : NavigationEvent(phoneState) { - /* - * Don't remove any fields, cause they should match with - * the schema downloaded from S3. Look at {@link SchemaTest} - */ - var arrivalTimestamp: String? = null - var rating: Int = 0 - var comment: String = "" - - override fun getEventName(): String = NavigationMetrics.CANCEL_SESSION - - override fun customFields(): Map = hashMapOf().also { fields -> - arrivalTimestamp?.let { fields["arrivalTimestamp"] = it.toValue() } - fields["rating"] = rating.toValue() - fields["comment"] = comment.toValue() - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationCustomEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationCustomEvent.kt deleted file mode 100644 index dd1a5186a83..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationCustomEvent.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import android.os.Build -import androidx.annotation.CallSuper -import com.google.gson.Gson -import com.mapbox.bindgen.Value -import com.mapbox.common.TelemetrySystemUtils -import com.mapbox.navigation.base.internal.metric.MetricEventInternal -import com.mapbox.navigation.base.metrics.NavigationMetrics - -@SuppressLint("ParcelCreator") -internal class NavigationCustomEvent : MetricEventInternal { - - private companion object { - private val OPERATING_SYSTEM = "Android - ${Build.VERSION.RELEASE}" - } - - var type: String = "" - var payload: String? = null - val version: String = "2.4" - var customEventVersion: String = "1.0.0" - val event: String = NavigationMetrics.CUSTOM_EVENT - - val created: String = TelemetrySystemUtils.obtainCurrentDate() - var createdMonotime: Int = 0 - val operatingSystem: String = OPERATING_SYSTEM - val device: String? = Build.MODEL - var driverMode: String = "" - - // Setting driverModeId to the following to match the pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}?" - val driverModeId: String = "00000000-0000-0000-0000-000000000000" - val driverModeStartTimestamp: String = "non_valid" - var driverModeStartTimestampMonotime: Int = 0 - var sdkIdentifier: String? = null - var eventVersion: Int = 0 - var simulation: Boolean = false - var locationEngine: String? = null - var lat: Double = 0.toDouble() - var lng: Double = 0.toDouble() - - override val metricName: String - get() = NavigationMetrics.CUSTOM_EVENT - - override fun toJson(gson: Gson): String = gson.toJson(this) - - @CallSuper - override fun toValue(): Value { - val fields = hashMapOf() - - fields["type"] = type.toValue() - payload?.let { fields["payload"] = it.toValue() } - fields["version"] = version.toValue() - fields["customEventVersion"] = customEventVersion.toValue() - fields["event"] = event.toValue() - fields["created"] = created.toValue() - fields["createdMonotime"] = createdMonotime.toValue() - fields["operatingSystem"] = operatingSystem.toValue() - device?.let { fields["device"] = it.toValue() } - fields["driverMode"] = driverMode.toValue() - fields["driverModeId"] = driverModeId.toValue() - fields["driverModeStartTimestamp"] = driverModeStartTimestamp.toValue() - fields["driverModeStartTimestampMonotime"] = driverModeStartTimestampMonotime.toValue() - sdkIdentifier?.let { fields["sdkIdentifier"] = it.toValue() } - fields["eventVersion"] = eventVersion.toValue() - fields["simulation"] = simulation.toValue() - locationEngine?.let { fields["locationEngine"] = it.toValue() } - fields["lat"] = lat.toValue() - fields["lng"] = lng.toValue() - return Value.valueOf(fields) - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationDepartEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationDepartEvent.kt deleted file mode 100644 index 51fbfb72f00..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationDepartEvent.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import com.mapbox.bindgen.Value -import com.mapbox.navigation.base.metrics.NavigationMetrics - -@SuppressLint("ParcelCreator") -internal class NavigationDepartEvent( - phoneState: PhoneState -) : NavigationEvent(phoneState) { - - override fun getEventName(): String = NavigationMetrics.DEPART - - override fun customFields(): Map? = null -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationEvent.kt deleted file mode 100644 index ead1909f8f7..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationEvent.kt +++ /dev/null @@ -1,155 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.os.Build -import androidx.annotation.CallSuper -import com.google.gson.Gson -import com.mapbox.bindgen.Value -import com.mapbox.common.TelemetrySystemUtils -import com.mapbox.navigation.base.internal.metric.MetricEventInternal -import com.mapbox.navigation.core.BuildConfig - -/** - * Base Event class for navigation events, contains common properties. - * - * @property driverMode one of [FeedbackEvent.DriverMode] - * @property sessionIdentifier id of [driverMode] - * @property startTimestamp start time of [driverMode] - * @property navigatorSessionIdentifier group id of modes under one Telemetry session - */ -internal abstract class NavigationEvent( - phoneState: PhoneState -) : MetricEventInternal { - - private companion object { - private val OPERATING_SYSTEM = "Android - ${Build.VERSION.RELEASE}" - } - - /* - * Don't remove any fields, cause they should match with - * the schema downloaded from S3. Look at {@link SchemaTest} - */ - val version = "2.2" - val operatingSystem: String = OPERATING_SYSTEM - val device: String? = Build.MODEL - val sdkVersion: String = BuildConfig.MAPBOX_NAVIGATION_VERSION_NAME - val created: String = TelemetrySystemUtils.obtainCurrentDate() // Schema pattern - val volumeLevel: Int = phoneState.volumeLevel - val batteryLevel: Int = phoneState.batteryLevel - val screenBrightness: Int = phoneState.screenBrightness - val batteryPluggedIn: Boolean = phoneState.isBatteryPluggedIn - val connectivity: String = phoneState.connectivity - val audioType: String = phoneState.audioType - val applicationState: String = phoneState.applicationState // Schema minLength 1 - val event: String = getEventName() - - var sdkIdentifier: String? = null - - var navigatorSessionIdentifier: String? = null - var startTimestamp: String? = null - var driverMode: String? = null - var sessionIdentifier: String? = null - var geometry: String? = null - var profile: String? = null - var originalRequestIdentifier: String? = null - var requestIdentifier: String? = null - var originalGeometry: String? = null - var locationEngine: String? = null - var tripIdentifier: String? = null - var lat: Double = 0.toDouble() - var lng: Double = 0.toDouble() - var simulation: Boolean = false - var absoluteDistanceToDestination: Int = 0 - var percentTimeInPortrait: Int = 0 - var percentTimeInForeground: Int = 0 - var distanceCompleted: Int = 0 - var distanceRemaining: Int = 0 - var durationRemaining: Int = 0 - var eventVersion: Int = 0 - var estimatedDistance: Int = 0 - var estimatedDuration: Int = 0 - var rerouteCount: Int = 0 - var originalEstimatedDistance: Int = 0 - var originalEstimatedDuration: Int = 0 - var stepCount: Int = 0 - var originalStepCount: Int = 0 - var legIndex: Int = 0 - var legCount: Int = 0 - var stepIndex: Int = 0 - var voiceIndex: Int = 0 - var bannerIndex: Int = 0 - var totalStepCount: Int = 0 - var appMetadata: AppMetadata? = null - - internal abstract fun getEventName(): String - - override fun toJson(gson: Gson): String = gson.toJson(this) - - override val metricName: String - get() = getEventName() - - @CallSuper - override fun toValue(): Value { - val fields = hashMapOf() - - fields["version"] = version.toValue() - fields["operatingSystem"] = operatingSystem.toValue() - device?.let { fields["device"] = it.toValue() } - fields["sdkVersion"] = sdkVersion.toValue() - fields["created"] = created.toValue() - fields["volumeLevel"] = volumeLevel.toValue() - fields["batteryLevel"] = batteryLevel.toValue() - fields["screenBrightness"] = screenBrightness.toValue() - fields["batteryPluggedIn"] = batteryPluggedIn.toValue() - fields["connectivity"] = connectivity.toValue() - fields["audioType"] = audioType.toValue() - fields["applicationState"] = applicationState.toValue() - fields["event"] = event.toValue() - - sdkIdentifier?.let { fields["sdkIdentifier"] = it.toValue() } - - navigatorSessionIdentifier?.let { fields["navigatorSessionIdentifier"] = it.toValue() } - startTimestamp?.let { fields["startTimestamp"] = it.toValue() } - driverMode?.let { fields["driverMode"] = it.toValue() } - sessionIdentifier?.let { fields["sessionIdentifier"] = it.toValue() } - geometry?.let { fields["geometry"] = it.toValue() } - profile?.let { fields["profile"] = it.toValue() } - originalRequestIdentifier?.let { fields["originalRequestIdentifier"] = it.toValue() } - requestIdentifier?.let { fields["requestIdentifier"] = it.toValue() } - originalGeometry?.let { fields["originalGeometry"] = it.toValue() } - locationEngine?.let { fields["locationEngine"] = it.toValue() } - tripIdentifier?.let { fields["tripIdentifier"] = it.toValue() } - fields["lat"] = lat.toValue() - fields["lng"] = lng.toValue() - fields["simulation"] = simulation.toValue() - fields["absoluteDistanceToDestination"] = absoluteDistanceToDestination.toValue() - fields["percentTimeInPortrait"] = percentTimeInPortrait.toValue() - fields["percentTimeInForeground"] = percentTimeInForeground.toValue() - fields["distanceCompleted"] = distanceCompleted.toValue() - fields["distanceRemaining"] = distanceRemaining.toValue() - fields["durationRemaining"] = durationRemaining.toValue() - fields["eventVersion"] = eventVersion.toValue() - fields["estimatedDistance"] = estimatedDistance.toValue() - fields["estimatedDuration"] = estimatedDuration.toValue() - fields["rerouteCount"] = rerouteCount.toValue() - fields["originalEstimatedDistance"] = originalEstimatedDistance.toValue() - fields["originalEstimatedDuration"] = originalEstimatedDuration.toValue() - fields["stepCount"] = stepCount.toValue() - fields["originalStepCount"] = originalStepCount.toValue() - fields["legIndex"] = legIndex.toValue() - fields["legCount"] = legCount.toValue() - fields["stepIndex"] = stepIndex.toValue() - fields["voiceIndex"] = voiceIndex.toValue() - fields["bannerIndex"] = bannerIndex.toValue() - fields["totalStepCount"] = totalStepCount.toValue() - appMetadata?.let { fields["appMetadata"] = it.toValue() } - - fields.putAll(customFields() ?: emptyMap()) - - return Value.valueOf(fields) - } - - /** - * Provide custom fields here or `null` if they are not needed - */ - protected abstract fun customFields(): Map? -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationFeedbackEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationFeedbackEvent.kt deleted file mode 100644 index a2a11c2c7c3..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationFeedbackEvent.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import com.mapbox.bindgen.Value -import com.mapbox.navigation.base.metrics.NavigationMetrics - -@SuppressLint("ParcelCreator") -internal class NavigationFeedbackEvent( - phoneState: PhoneState, - navigationStepData: NavigationStepData -) : NavigationEvent(phoneState) { - /* - * Don't remove any fields, cause they should match with - * the schema downloaded from S3. Look at {@link SchemaTest} - */ - val userId: String = phoneState.userId - val feedbackId: String = phoneState.feedbackId - val step: NavigationStepData = navigationStepData - var feedbackType: String? = null - var source: String? = null - var description: String? = null - var locationsBefore: Array? = emptyArray() - var locationsAfter: Array? = emptyArray() - var screenshot: String? = null - var feedbackSubType: Array? = emptyArray() - - override fun getEventName(): String = NavigationMetrics.FEEDBACK - - override fun customFields(): Map = hashMapOf().also { fields -> - fields["userId"] = userId.toValue() - fields["feedbackId"] = feedbackId.toValue() - fields["step"] = step.toValue() - feedbackType?.let { fields["feedbackType"] = it.toValue() } - source?.let { fields["source"] = it.toValue() } - description?.let { fields["description"] = it.toValue() } - locationsBefore?.let { fields["locationsBefore"] = it.toValue { toValue() } } - locationsAfter?.let { fields["locationsAfter"] = it.toValue { toValue() } } - screenshot?.let { fields["screenshot"] = it.toValue() } - feedbackSubType?.let { fields["feedbackSubType"] = it.toValue { toValue() } } - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationFreeDriveEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationFreeDriveEvent.kt deleted file mode 100644 index bd2f761be84..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationFreeDriveEvent.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import com.google.gson.Gson -import com.mapbox.bindgen.Value -import com.mapbox.common.TelemetrySystemUtils -import com.mapbox.navigation.base.internal.metric.MetricEventInternal -import com.mapbox.navigation.base.metrics.NavigationMetrics - -/** - * Navigation free drive event. - * - * Note: class doesn't have driverMode property because it's always FreeDrive. - * - * @property sessionIdentifier unique id of FreeDrive(same for start and stop events) - * @property startTimestamp start time FreeDrive event - * @property navigatorSessionIdentifier group id of modes(FreeDrive/ActiveGuidance) under one Telemetry session - */ -@SuppressLint("ParcelCreator") -internal class NavigationFreeDriveEvent( - phoneState: PhoneState -) : MetricEventInternal { - - /* - * Don't remove any fields, cause they should match with - * the schema downloaded from S3. Look at {@link SchemaTest} - */ - val version = "2.2" - val created: String = TelemetrySystemUtils.obtainCurrentDate() // Schema pattern - val volumeLevel: Int = phoneState.volumeLevel - val batteryLevel: Int = phoneState.batteryLevel - val screenBrightness: Int = phoneState.screenBrightness - val batteryPluggedIn: Boolean = phoneState.isBatteryPluggedIn - val connectivity: String = phoneState.connectivity - val audioType: String = phoneState.audioType - val applicationState: String = phoneState.applicationState // Schema minLength 1 - val event: String = NavigationMetrics.FREE_DRIVE - var eventVersion: Int = 0 - var locationEngine: String? = null - var percentTimeInPortrait: Int = 0 - var percentTimeInForeground: Int = 0 - var simulation: Boolean = false - - var navigatorSessionIdentifier: String? = null // group id of modes under one Telemetry session - var startTimestamp: String? = null // mode start time - var sessionIdentifier: String? = null // mode id - - var location: TelemetryLocation? = null - var eventType: String? = null - var appMetadata: AppMetadata? = null - - override val metricName: String - get() = NavigationMetrics.FREE_DRIVE - - override fun toJson(gson: Gson): String = gson.toJson(this) - - override fun toValue(): Value { - val fields = hashMapOf() - - fields["version"] = version.toValue() - fields["created"] = created.toValue() - fields["volumeLevel"] = volumeLevel.toValue() - fields["batteryLevel"] = batteryLevel.toValue() - fields["screenBrightness"] = screenBrightness.toValue() - fields["batteryPluggedIn"] = batteryPluggedIn.toValue() - fields["connectivity"] = connectivity.toValue() - fields["audioType"] = audioType.toValue() - fields["applicationState"] = applicationState.toValue() - fields["event"] = event.toValue() - fields["eventVersion"] = eventVersion.toValue() - locationEngine?.let { fields["locationEngine"] = it.toValue() } - fields["percentTimeInPortrait"] = percentTimeInPortrait.toValue() - fields["percentTimeInForeground"] = percentTimeInForeground.toValue() - fields["simulation"] = simulation.toValue() - navigatorSessionIdentifier?.let { fields["navigatorSessionIdentifier"] = it.toValue() } - startTimestamp?.let { fields["startTimestamp"] = it.toValue() } - sessionIdentifier?.let { fields["sessionIdentifier"] = it.toValue() } - location?.let { fields["location"] = it.toValue() } - eventType?.let { fields["eventType"] = it.toValue() } - appMetadata?.let { fields["appMetadata"] = it.toValue() } - - return Value.valueOf(fields) - } -} - -internal enum class FreeDriveEventType(val type: String) { - START("start"), - STOP("stop") -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationRerouteEvent.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationRerouteEvent.kt deleted file mode 100644 index deba3da490a..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationRerouteEvent.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.annotation.SuppressLint -import com.mapbox.bindgen.Value -import com.mapbox.navigation.base.metrics.NavigationMetrics - -@SuppressLint("ParcelCreator") -internal class NavigationRerouteEvent( - phoneState: PhoneState, - navigationStepData: NavigationStepData -) : NavigationEvent(phoneState) { - /* - * Don't remove any fields, cause they should match with - * the schema downloaded from S3. Look at {@link SchemaTest} - */ - var newDistanceRemaining: Int = 0 - var newDurationRemaining: Int = 0 - val feedbackId: String = phoneState.feedbackId - var newGeometry: String? = null - val step: NavigationStepData = navigationStepData - var secondsSinceLastReroute: Int = 0 - var locationsBefore: Array? = emptyArray() - var locationsAfter: Array? = emptyArray() - var screenshot: String? = null - - override fun getEventName(): String = NavigationMetrics.REROUTE - - override fun customFields(): Map? = hashMapOf().also { fields -> - fields["newDistanceRemaining"] = newDistanceRemaining.toValue() - fields["newDurationRemaining"] = newDurationRemaining.toValue() - fields["feedbackId"] = feedbackId.toValue() - newGeometry?.let { fields["newGeometry"] = it.toValue() } - fields["step"] = step.toValue() - fields["secondsSinceLastReroute"] = secondsSinceLastReroute.toValue() - locationsBefore?.let { fields["locationsBefore"] = it.toValue { toValue() } } - locationsAfter?.let { fields["locationsAfter"] = it.toValue { toValue() } } - screenshot?.let { fields["screenshot"] = it.toValue() } - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationStepData.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationStepData.kt deleted file mode 100644 index 3b89635b798..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/NavigationStepData.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -/** - * Class that contains step meta data - */ -internal class NavigationStepData(metricsRouteProgress: MetricsRouteProgress) { - val upcomingInstruction: String? = - metricsRouteProgress.upcomingStepInstruction?.ifBlank { null } // Schema minLength 1 - val upcomingModifier: String? = metricsRouteProgress.upcomingStepModifier - val upcomingName: String? = metricsRouteProgress.upcomingStepName - val upcomingType: String? = - metricsRouteProgress.upcomingStepType?.ifBlank { null } // Schema minLength 1 - val previousInstruction: String? = - metricsRouteProgress.previousStepInstruction?.ifBlank { null } // Schema minLength 1 - val previousModifier: String? = metricsRouteProgress.previousStepModifier - val previousName: String = metricsRouteProgress.previousStepName - val previousType: String? = - metricsRouteProgress.previousStepType?.ifBlank { null } // Schema minLength 1 - val distance: Int = metricsRouteProgress.currentStepDistance - val duration: Int = metricsRouteProgress.currentStepDuration - val distanceRemaining: Int = metricsRouteProgress.currentStepDistanceRemaining - val durationRemaining: Int = metricsRouteProgress.currentStepDurationRemaining - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as NavigationStepData - - if (upcomingInstruction != other.upcomingInstruction) return false - if (upcomingModifier != other.upcomingModifier) return false - if (upcomingName != other.upcomingName) return false - if (upcomingType != other.upcomingType) return false - if (previousInstruction != other.previousInstruction) return false - if (previousModifier != other.previousModifier) return false - if (previousName != other.previousName) return false - if (previousType != other.previousType) return false - if (distance != other.distance) return false - if (duration != other.duration) return false - if (distanceRemaining != other.distanceRemaining) return false - if (durationRemaining != other.durationRemaining) return false - - return true - } - - override fun hashCode(): Int { - var result = upcomingInstruction.hashCode() - result = 31 * result + upcomingModifier.hashCode() - result = 31 * result + upcomingName.hashCode() - result = 31 * result + upcomingType.hashCode() - result = 31 * result + previousInstruction.hashCode() - result = 31 * result + previousModifier.hashCode() - result = 31 * result + previousName.hashCode() - result = 31 * result + previousType.hashCode() - result = 31 * result + distance - result = 31 * result + duration - result = 31 * result + distanceRemaining - result = 31 * result + durationRemaining - return result - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/PhoneState.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/PhoneState.kt deleted file mode 100644 index dfa67be6597..00000000000 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/PhoneState.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.content.Context -import com.mapbox.common.TelemetrySystemUtils.isPluggedIn -import com.mapbox.common.TelemetrySystemUtils.obtainApplicationState -import com.mapbox.common.TelemetrySystemUtils.obtainBatteryLevel -import com.mapbox.common.TelemetrySystemUtils.obtainCellularNetworkType -import com.mapbox.common.TelemetrySystemUtils.obtainCurrentDate -import com.mapbox.common.TelemetrySystemUtils.obtainUniversalUniqueIdentifier -import com.mapbox.navigation.core.telemetry.obtainAudioType -import com.mapbox.navigation.core.telemetry.obtainScreenBrightness -import com.mapbox.navigation.core.telemetry.obtainVolumeLevel - -/** - * Class that will hold the current states of the device phone. - */ -internal data class PhoneState( - val volumeLevel: Int, - val batteryLevel: Int, - val screenBrightness: Int, - val isBatteryPluggedIn: Boolean, - val connectivity: String, - val audioType: String, - val applicationState: String, - val created: String, - val feedbackId: String, - val userId: String, -) { - internal companion object { - internal fun newInstance(context: Context): PhoneState = - PhoneState( - volumeLevel = obtainVolumeLevel(context), - batteryLevel = obtainBatteryLevel(context), - screenBrightness = obtainScreenBrightness(context), - isBatteryPluggedIn = isPluggedIn(context), - connectivity = obtainCellularNetworkType(context), - audioType = obtainAudioType(context), - applicationState = obtainApplicationState(context), - created = obtainCurrentDate(), - feedbackId = obtainUniversalUniqueIdentifier(), - // Hardcoded to '-' for privacy concerns - userId = "-", - ) - } -} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/UserFeedback.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/UserFeedback.kt new file mode 100644 index 00000000000..4e89a735400 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/telemetry/events/UserFeedback.kt @@ -0,0 +1,104 @@ +package com.mapbox.navigation.core.telemetry.events + +import android.graphics.Bitmap +import com.mapbox.bindgen.DataRef +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.internal.telemetry.UserFeedbackInternal +import com.mapbox.navigation.core.telemetry.events.UserFeedback.Companion.toNative +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) +class UserFeedback private constructor( + val feedbackType: String, + val description: String, + val feedbackSubTypes: Array, + val screenshot: Bitmap?, + val feedbackMetadata: FeedbackMetadata? = null +) { + + internal companion object { + internal fun UserFeedback.toNative(): com.mapbox.navigator.UserFeedback = + com.mapbox.navigator.UserFeedback( + feedbackType, + feedbackSubTypes.toList(), + "", + description, + screenshot?.let { bitmap -> + val out = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out) + val byteBuffer = ByteBuffer.allocateDirect(out.size()) + byteBuffer.put(out.toByteArray()) + return@let DataRef(byteBuffer) + } + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UserFeedback + + if (feedbackType != other.feedbackType) return false + if (description != other.description) return false + if (!feedbackSubTypes.contentEquals(other.feedbackSubTypes)) return false + if (screenshot != other.screenshot) return false + if (feedbackMetadata != other.feedbackMetadata) return false + + return true + } + + override fun hashCode(): Int { + var result = feedbackType.hashCode() + result = 31 * result + description.hashCode() + result = 31 * result + feedbackSubTypes.contentHashCode() + result = 31 * result + screenshot.hashCode() + result = 31 * result + feedbackMetadata.hashCode() + return result + } + + override fun toString(): String { + return "UserFeedback(" + + "feedbackType='$feedbackType', " + + "description='$description', " + + "feedbackSubType=${feedbackSubTypes.contentToString()}, " + + "screenshot=$screenshot" + + "feedbackMetadata=$feedbackMetadata" + + ")" + } + + /** + * Builder [UserFeedback] + */ + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) + class Builder( + private val feedbackType: String, + private val description: String, + ) { + private var screenshot: Bitmap? = null + private var feedbackSubTypes: Array = emptyArray() + private var feedbackMetadata: FeedbackMetadata? = null + + fun screenshot(screenshot: Bitmap) = apply { + this.screenshot = screenshot + } + + fun feedbackSubTypes(feedbackSubTypes: Array) = apply { + this.feedbackSubTypes = feedbackSubTypes + } + + @ExperimentalPreviewMapboxNavigationAPI + fun feedbackMetadata(feedbackMetadata: FeedbackMetadata) = apply { + this.feedbackMetadata = feedbackMetadata + } + + fun build(): UserFeedback = UserFeedback( + feedbackType, + description, + feedbackSubTypes, + screenshot, + feedbackMetadata, + ) + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/service/NavigationNotificationService.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/service/NavigationNotificationService.kt index 302a255a7c9..f716e5decef 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/service/NavigationNotificationService.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/service/NavigationNotificationService.kt @@ -9,7 +9,6 @@ import androidx.annotation.CallSuper import androidx.core.app.ServiceCompat import com.mapbox.navigation.core.internal.dump.MapboxDumpHandler import com.mapbox.navigation.core.internal.dump.MapboxDumpRegistry -import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry import java.io.FileDescriptor import java.io.PrintWriter @@ -46,7 +45,6 @@ internal class NavigationNotificationService : Service() { * @return Int */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - MapboxNavigationTelemetry.setApplicationInstance(application) MapboxTripService.registerOneTimeNotificationDataObserver(notificationDataObserver) return START_STICKY } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt index 702afb819d5..a192a9722df 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt @@ -1,6 +1,7 @@ package com.mapbox.navigation.core import android.app.AlarmManager +import android.app.Application import android.app.NotificationManager import android.content.Context import android.net.ConnectivityManager @@ -30,7 +31,7 @@ import com.mapbox.navigation.core.routealternatives.RouteAlternativesController import com.mapbox.navigation.core.routealternatives.RouteAlternativesControllerProvider import com.mapbox.navigation.core.routerefresh.RouteRefreshController import com.mapbox.navigation.core.routerefresh.RouteRefreshControllerProvider -import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry +import com.mapbox.navigation.core.telemetry.NavigationTelemetry import com.mapbox.navigation.core.testutil.createRoutesUpdatedResult import com.mapbox.navigation.core.trip.service.TripService import com.mapbox.navigation.core.trip.session.NativeSetRouteValue @@ -55,6 +56,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.mockkClass import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.runs @@ -82,7 +84,10 @@ internal open class MapboxNavigationBaseTest { val accessToken = "pk.1234" val directionsSession: DirectionsSession = mockk(relaxUnitFun = true) val cache: CacheHandle = mockk(relaxUnitFun = true) - val navigator: MapboxNativeNavigator = mockk(relaxUnitFun = true) + val navigator: MapboxNativeNavigator = mockk(relaxUnitFun = true) { + every { experimental } returns mockk() + } + val navigationTelemetry: NavigationTelemetry = mockk(relaxUnitFun = true) val tripService: TripService = mockk(relaxUnitFun = true) val tripSession: TripSession = mockk(relaxUnitFun = true) val locationEngine: LocationEngine = mockk(relaxUnitFun = true) @@ -107,7 +112,7 @@ internal open class MapboxNavigationBaseTest { val routesPreviewController = mockk(relaxed = true) val routesCacheClearer = mockk(relaxed = true) - val applicationContext: Context = mockk(relaxed = true) { + val applicationContext: Application = mockk(relaxed = true) { every { inferDeviceLocale() } returns Locale.US every { getSystemService(Context.NOTIFICATION_SERVICE) @@ -116,7 +121,6 @@ internal open class MapboxNavigationBaseTest { every { packageManager } returns mockk(relaxed = true) every { packageName } returns "com.mapbox.navigation.core.MapboxNavigationTest" every { filesDir } returns File("some/path") - every { navigator.experimental } returns mockk() } lateinit var mapboxNavigation: MapboxNavigation @@ -230,6 +234,7 @@ internal open class MapboxNavigationBaseTest { any(), any(), any(), + any(), ) } returns navigator mockkObject(TelemetryUtilsDelegate) @@ -250,7 +255,7 @@ internal open class MapboxNavigationBaseTest { unmockkObject(CacheHandleWrapper) unmockkObject(RouteRefreshControllerProvider) unmockkObject(RouteAlternativesControllerProvider) - unmockkObject(MapboxNavigationTelemetry) + unmockkObject(NavigationTelemetry.Companion) unmockkObject(EventsServiceProvider) unmockkObject(TelemetryServiceProvider) unmockkObject(TelemetryUtilsDelegate) @@ -268,6 +273,7 @@ internal open class MapboxNavigationBaseTest { any(), any(), any(), + any(), ) } returns navigator coEvery { navigator.setRoutes(any(), any(), any(), any()) } answers { @@ -340,20 +346,10 @@ internal open class MapboxNavigationBaseTest { TelemetryServiceProvider.provideTelemetryService(any()) } returns mockk(relaxUnitFun = true) - mockkObject(MapboxNavigationTelemetry) - every { MapboxNavigationTelemetry.initialize(any(), any(), any(), any()) } just runs - every { MapboxNavigationTelemetry.destroy(any()) } just runs + mockkObject(NavigationTelemetry.Companion) every { - MapboxNavigationTelemetry.postUserFeedback( - any(), - any(), - any(), - any(), - any(), - any(), - any(), - ) - } just runs + NavigationTelemetry(any(), any()) + } returns navigationTelemetry } fun provideNavigationOptions() = diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationTest.kt index 161af1b0bab..299057b8298 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationTest.kt @@ -21,7 +21,6 @@ import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult import com.mapbox.navigation.core.internal.HistoryRecordingStateChangeObserver import com.mapbox.navigation.core.internal.extensions.registerHistoryRecordingStateChangeObserver import com.mapbox.navigation.core.internal.extensions.unregisterHistoryRecordingStateChangeObserver -import com.mapbox.navigation.core.internal.telemetry.NavigationCustomEventType import com.mapbox.navigation.core.navigator.CacheHandleWrapper import com.mapbox.navigation.core.preview.RoutesPreview import com.mapbox.navigation.core.reroute.InternalRerouteController @@ -30,6 +29,8 @@ import com.mapbox.navigation.core.reroute.RerouteController import com.mapbox.navigation.core.reroute.RerouteState import com.mapbox.navigation.core.routerefresh.RouteRefreshObserver import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry +import com.mapbox.navigation.core.routerefresh.RefreshedRouteInfo +import com.mapbox.navigation.core.routerefresh.RouteRefreshStatesObserver import com.mapbox.navigation.core.testutil.createRoutesUpdatedResult import com.mapbox.navigation.core.trip.session.LocationObserver import com.mapbox.navigation.core.trip.session.NativeSetRouteError @@ -370,69 +371,6 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { verify(exactly = 1) { navigationSession.unregisterAllNavigationSessionStateObservers() } } - @Test - fun telemetryIsDisabled() { - every { TelemetryUtilsDelegate.getEventsCollectionState() } returns false - - createMapboxNavigation() - mapboxNavigation.onDestroy() - - verify(exactly = 0) { - MapboxNavigationTelemetry.initialize(any(), any(), any(), any()) - } - verify(exactly = 0) { MapboxNavigationTelemetry.destroy(any()) } - } - - @ExperimentalPreviewMapboxNavigationAPI - @Test(expected = IllegalStateException::class) - fun telemetryIsDisabledTryToGetFeedbackMetadataWrapper() { - every { TelemetryUtilsDelegate.getEventsCollectionState() } returns false - - createMapboxNavigation() - mapboxNavigation.provideFeedbackMetadataWrapper() - } - - @ExperimentalPreviewMapboxNavigationAPI - fun telemetryIsDisabledTryToPostFeedback() { - every { TelemetryUtilsDelegate.getEventsCollectionState() } returns false - - createMapboxNavigation() - - mapboxNavigation.postUserFeedback(mockk(), mockk(), mockk(), mockk(), mockk()) - mapboxNavigation.postUserFeedback(mockk(), mockk(), mockk(), mockk(), mockk(), mockk()) - - verify(exactly = 0) { - MapboxNavigationTelemetry.postUserFeedback( - any(), - any(), - any(), - any(), - any(), - any(), - any(), - ) - } - } - - @Test - fun unregisterAllTelemetryObservers() { - createMapboxNavigation() - mapboxNavigation.onDestroy() - - verify(exactly = 1) { MapboxNavigationTelemetry.destroy(eq(mapboxNavigation)) } - } - - @Test - fun unregisterAllTelemetryObserversIsCalledAfterTripSessionStop() { - createMapboxNavigation() - mapboxNavigation.onDestroy() - - verifyOrder { - tripSession.stop() - MapboxNavigationTelemetry.destroy(mapboxNavigation) - } - } - @Test fun unregisterAllArrivalObservers() { createMapboxNavigation() @@ -830,6 +768,7 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { capture(slot), any(), any(), + any(), ) } returns navigator val options = navigationOptions.toBuilder() @@ -852,6 +791,7 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { capture(slot), any(), any(), + any(), ) } returns navigator val options = navigationOptions.toBuilder() @@ -1144,6 +1084,7 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { capture(slot), any(), any(), + any(), ) } returns navigator val tilesVersion = "tilesVersion" @@ -1185,6 +1126,7 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { capture(tileConfigSlot), any(), any(), + any(), ) } just Runs @@ -1217,16 +1159,17 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { mapboxNavigation = MapboxNavigation(navigationOptions, threadController) - val tileConfigSlot = slot() - every { - navigator.recreate( - any(), - any(), - capture(tileConfigSlot), - any(), - any(), - ) - } just Runs + val tileConfigSlot = slot() + every { + navigator.recreate( + any(), + any(), + capture(tileConfigSlot), + any(), + any(), + any(), + ) + } just Runs fallbackObserverSlot.captured.onCanReturnToLatest("") @@ -1914,30 +1857,6 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() { assertNull(mapboxNavigation.getRerouteController()) } - @Test - fun `when telemetry is enabled custom event is posted`() = coroutineRule.runBlockingTest { - createMapboxNavigation() - every { TelemetryUtilsDelegate.getEventsCollectionState() } returns true - every { MapboxNavigationTelemetry.postCustomEvent(any(), any(), any()) } just Runs - every { MapboxNavigationTelemetry.destroy(any()) } just Runs - - mapboxNavigation.postCustomEvent("", NavigationCustomEventType.ANALYTICS, "1.0") - - verify(exactly = 1) { MapboxNavigationTelemetry.postCustomEvent(any(), any(), any()) } - } - - @Test - fun `when telemetry is disabled custom event is not posted`() = coroutineRule.runBlockingTest { - createMapboxNavigation() - every { TelemetryUtilsDelegate.getEventsCollectionState() } returns false - every { MapboxNavigationTelemetry.postCustomEvent(any(), any(), any()) } just Runs - every { MapboxNavigationTelemetry.destroy(any()) } just Runs - - mapboxNavigation.postCustomEvent("", NavigationCustomEventType.ANALYTICS, "1.0") - - verify(exactly = 0) { MapboxNavigationTelemetry.postCustomEvent(any(), any(), any()) } - } - @Test fun requestRoadGraphDataUpdate() { val callback = mockk() diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/LocationsCollectorTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/LocationsCollectorTest.kt deleted file mode 100644 index 229637204a2..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/LocationsCollectorTest.kt +++ /dev/null @@ -1,151 +0,0 @@ -package com.mapbox.navigation.core.telemetry - -import android.location.Location -import com.mapbox.navigation.testing.LoggingFrontendTestRule -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Rule -import org.junit.Test - -@ExperimentalCoroutinesApi -class LocationsCollectorTest { - - @get:Rule - val loggerRule = LoggingFrontendTestRule() - - private val locationsCollector = LocationsCollectorImpl() - - @Test - fun ignoreEnhancedLocationUpdates() { - locationsCollector.onNewLocationMatcherResult(mockk()) - - assertNull(locationsCollector.lastLocation) - } - - @Test - fun useRawLocationUpdates() { - val rawLocation: Location = mockk() - locationsCollector.onNewRawLocation(rawLocation) - - assertEquals(rawLocation, locationsCollector.lastLocation) - } - - @Test - fun lastLocation() = runBlocking { - val firstLocation = mockk() - val secondLocation = mockk() - - locationsCollector.onNewRawLocation(firstLocation) - assertEquals(firstLocation, locationsCollector.lastLocation) - - locationsCollector.onNewRawLocation(secondLocation) - assertEquals(secondLocation, locationsCollector.lastLocation) - } - - @Test - fun preAndPostLocationsOrder() = runBlocking { - val preEventLocation = mockk() - val postEventLocation = mockk() - - locationsCollector.onNewRawLocation(preEventLocation) - locationsCollector.collectLocations { preEventLocations, postEventLocations -> - assertEquals(1, preEventLocations.size) - assertEquals(preEventLocation, preEventLocations[0]) - - assertEquals(1, postEventLocations.size) - assertEquals(postEventLocation, postEventLocations[0]) - } - locationsCollector.onNewRawLocation(postEventLocation) - locationsCollector.flushBuffers() - } - - @Test - fun preAndPostLocationsMaxSize() = runBlocking { - repeat(25) { locationsCollector.onNewRawLocation(mockk()) } - locationsCollector.collectLocations { preEventLocations, postEventLocations -> - assertEquals(20, preEventLocations.size) - assertEquals(20, postEventLocations.size) - } - repeat(25) { locationsCollector.onNewRawLocation(mockk()) } - locationsCollector.flushBuffers() - } - - @Test - fun prePostLocationsEvents() = runBlocking { - val l = mutableListOf() - repeat(42) { l.add(mockk()) } - - // before any location posted. preList will be empty. postList will have 20 items - locationsCollector.collectLocations { preLocations, postLocations -> - val preList = emptyList() - val postList = mutableListOf().apply { for (i in 0 until 20) add(l[i]) } - assertEquals(preList, preLocations) - assertEquals(postList, postLocations) - } - - for (i in 0 until 5) locationsCollector.onNewRawLocation(l[i]) - - // 5 locations posted. preList will have all of them. postList will have 20 items - locationsCollector.collectLocations { preLocations, postLocations -> - val preList = mutableListOf().apply { for (i in 0 until 5) add(l[i]) } - val postList = mutableListOf().apply { for (i in 5 until 25) add(l[i]) } - assertEquals(preList, preLocations) - assertEquals(postList, postLocations) - } - - for (i in 5 until 17) locationsCollector.onNewRawLocation(l[i]) - - // 17 locations posted. preList will have all of them. postList will have 20 items - locationsCollector.collectLocations { preLocations, postLocations -> - val preList = mutableListOf().apply { for (i in 0 until 17) add(l[i]) } - val postList = mutableListOf().apply { for (i in 17 until 37) add(l[i]) } - assertEquals(preList, preLocations) - assertEquals(postList, postLocations) - } - - for (i in 17 until 29) locationsCollector.onNewRawLocation(l[i]) - - // 29 locations posted. preList will have the last 20. postList will have 13 items - locationsCollector.collectLocations { preLocations, postLocations -> - val preList = mutableListOf().apply { for (i in 9 until 29) add(l[i]) } - val postList = mutableListOf().apply { for (i in 29 until 42) add(l[i]) } - assertEquals(preList, preLocations) - assertEquals(postList, postLocations) - } - - for (i in 29 until 42) locationsCollector.onNewRawLocation(l[i]) - - // 42 locations posted. preList will have the last 20. postList will be empty - locationsCollector.collectLocations { preLocations, postLocations -> - val preList = mutableListOf().apply { for (i in 22 until 42) add(l[i]) } - val postList = emptyList() - assertEquals(preList, preLocations) - assertEquals(postList, postLocations) - } - - locationsCollector.flushBuffers() - } - - @Test - fun flushLocationForParticularListener() { - val mockLocationsListener1 = - mockk(relaxUnitFun = true) - val mockLocationsListener2 = - mockk(relaxUnitFun = true) - val mockLocationsListener3 = - mockk(relaxUnitFun = true) - - locationsCollector.collectLocations(mockLocationsListener1) - locationsCollector.collectLocations(mockLocationsListener2) - locationsCollector.collectLocations(mockLocationsListener3) - locationsCollector.flushBufferFor(mockLocationsListener2) - - verify(exactly = 1) { - mockLocationsListener2.onBufferFull(any(), any()) - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetryTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetryTest.kt index 114f5450859..e69de29bb2d 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetryTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/MapboxNavigationTelemetryTest.kt @@ -1,1725 +0,0 @@ -package com.mapbox.navigation.core.telemetry - -import android.app.ActivityManager -import android.app.AlarmManager -import android.content.Context -import android.location.Location -import android.media.AudioManager -import android.telephony.TelephonyManager -import com.mapbox.api.directions.v5.DirectionsCriteria -import com.mapbox.api.directions.v5.models.LegStep -import com.mapbox.api.directions.v5.models.RouteLeg -import com.mapbox.api.directions.v5.models.RouteOptions -import com.mapbox.api.directions.v5.models.StepManeuver -import com.mapbox.common.BillingServiceInterface -import com.mapbox.common.TelemetrySystemUtils -import com.mapbox.common.TurnstileEvent -import com.mapbox.common.UserSKUIdentifier -import com.mapbox.geojson.Point -import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI -import com.mapbox.navigation.base.metrics.MetricEvent -import com.mapbox.navigation.base.options.NavigationOptions -import com.mapbox.navigation.base.route.NavigationRoute -import com.mapbox.navigation.base.trip.model.RouteLegProgress -import com.mapbox.navigation.base.trip.model.RouteProgress -import com.mapbox.navigation.base.trip.model.RouteProgressState -import com.mapbox.navigation.base.trip.model.RouteProgressState.TRACKING -import com.mapbox.navigation.base.trip.model.RouteStepProgress -import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.accounts.BillingServiceProvider -import com.mapbox.navigation.core.arrival.ArrivalObserver -import com.mapbox.navigation.core.directions.session.RoutesExtra -import com.mapbox.navigation.core.directions.session.RoutesObserver -import com.mapbox.navigation.core.internal.accounts.MapboxNavigationAccounts -import com.mapbox.navigation.core.internal.telemetry.NavigationCustomEventType -import com.mapbox.navigation.core.internal.telemetry.UserFeedback -import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback -import com.mapbox.navigation.core.internal.telemetry.toTelemetryLocation -import com.mapbox.navigation.core.telemetry.events.AppMetadata -import com.mapbox.navigation.core.telemetry.events.FeedbackEvent -import com.mapbox.navigation.core.telemetry.events.FeedbackMetadata -import com.mapbox.navigation.core.telemetry.events.FreeDriveEventType.START -import com.mapbox.navigation.core.telemetry.events.FreeDriveEventType.STOP -import com.mapbox.navigation.core.telemetry.events.MetricsDirectionsRoute -import com.mapbox.navigation.core.telemetry.events.MetricsRouteProgress -import com.mapbox.navigation.core.telemetry.events.NavigationArriveEvent -import com.mapbox.navigation.core.telemetry.events.NavigationCancelEvent -import com.mapbox.navigation.core.telemetry.events.NavigationCustomEvent -import com.mapbox.navigation.core.telemetry.events.NavigationDepartEvent -import com.mapbox.navigation.core.telemetry.events.NavigationEvent -import com.mapbox.navigation.core.telemetry.events.NavigationFeedbackEvent -import com.mapbox.navigation.core.telemetry.events.NavigationFreeDriveEvent -import com.mapbox.navigation.core.telemetry.events.NavigationRerouteEvent -import com.mapbox.navigation.core.telemetry.events.PhoneState -import com.mapbox.navigation.core.telemetry.events.TelemetryLocation -import com.mapbox.navigation.core.testutil.createRoutesUpdatedResult -import com.mapbox.navigation.core.testutil.ifCaptured -import com.mapbox.navigation.core.trip.session.NavigationSessionState -import com.mapbox.navigation.core.trip.session.NavigationSessionState.ActiveGuidance -import com.mapbox.navigation.core.trip.session.NavigationSessionState.FreeDrive -import com.mapbox.navigation.core.trip.session.NavigationSessionState.Idle -import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver -import com.mapbox.navigation.core.trip.session.RouteProgressObserver -import com.mapbox.navigation.metrics.MapboxMetricsReporter -import com.mapbox.navigation.metrics.internal.EventsServiceProvider -import com.mapbox.navigation.metrics.internal.TelemetryServiceProvider -import com.mapbox.navigation.metrics.internal.TelemetryUtilsDelegate -import com.mapbox.navigation.testing.LoggingFrontendTestRule -import com.mapbox.navigation.testing.MainCoroutineRule -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.runs -import io.mockk.slot -import io.mockk.unmockkObject -import io.mockk.unmockkStatic -import io.mockk.verify -import junit.framework.TestCase.assertEquals -import junit.framework.TestCase.assertNotSame -import junit.framework.TestCase.assertSame -import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.InternalCoroutinesApi -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -@ExperimentalPreviewMapboxNavigationAPI -@InternalCoroutinesApi -@ExperimentalCoroutinesApi -class MapboxNavigationTelemetryTest { - - @get:Rule - val loggerRule = LoggingFrontendTestRule() - - private companion object { - private const val LAST_LOCATION_LAT = 55.5 - private const val LAST_LOCATION_LON = 88.8 - private const val LAST_LOCATION_SPEED = 10.0f - private const val LAST_LOCATION_BEARING = 90.0f - private const val LAST_LOCATION_ALTITUDE = 15.0 - private const val LAST_LOCATION_TIME = 1000L - private const val LAST_LOCATION_ACCURACY = 222.0f - private const val LAST_LOCATION_VERTICAL_ACCURACY = 111.0f - - private const val ANOTHER_LAST_LOCATION_LAT = 66.6 - private const val ANOTHER_LAST_LOCATION_LON = 77.7 - - private const val ORIGINAL_ROUTE_GEOMETRY = "" - private const val ORIGINAL_ROUTE_DISTANCE = 1.1 - private const val ORIGINAL_ROUTE_DURATION = 2.2 - private const val ORIGINAL_ROUTE_ROUTE_INDEX = 10 - - private const val ORIGINAL_ROUTE_OPTIONS_PROFILE = "original_profile" - private const val ORIGINAL_ROUTE_OPTIONS_REQUEST_UUID = "original_requestUuid" - - private const val ANOTHER_ROUTE_GEOMETRY = "" - private const val ANOTHER_ROUTE_ROUTE_INDEX = 1 - private const val ANOTHER_ROUTE_DISTANCE = 123.1 - private const val ANOTHER_ROUTE_DURATION = 235.2 - - private const val ANOTHER_ROUTE_OPTIONS_PROFILE = "progress_profile" - private const val ANOTHER_ROUTE_OPTIONS_REQUEST_UUID = "progress_requestUuid" - - private const val ROUTE_PROGRESS_DISTANCE_REMAINING = 11f - private const val ROUTE_PROGRESS_DURATION_REMAINING = 22.22 - private const val ROUTE_PROGRESS_DISTANCE_TRAVELED = 15f - - private const val ORIGINAL_STEP_MANEUVER_LOCATION_LATITUDE = 135.21 - private const val ORIGINAL_STEP_MANEUVER_LOCATION_LONGITUDE = 436.5 - private const val ANOTHER_STEP_MANEUVER_LOCATION_LATITUDE = 42.2 - private const val ANOTHER_STEP_MANEUVER_LOCATION_LONGITUDE = 12.4 - - private const val STEP_INDEX = 5 - private const val SDK_IDENTIFIER = "mapbox-navigation-android" - private const val ACTIVE_GUIDANCE_SESSION_ID = "active-guidance-session-id" - private const val FREE_DRIVE_SESSION_ID = "free-drive-session-id" - - private const val DEFAULT_FEEDBACK_ID = "default feedback id" - private const val FEEDBACK_TYPE = "feedback type" - private const val DESCRIPTION = "feedback description" - private const val FEEDBACK_SOURCE = "feedback source" - private const val SCREENSHOT = "feedback screenshot" - private val FEEDBACK_SUBTYPE = arrayOf("feedback subtype") - - /** - * Since [MapboxNavigationAccounts] is a singleton, it will effectively obtain - * an instance of [BillingServiceInterface] from the mocked [BillingServiceProvider] - * only once when initializing the first test. - * - * That instance of returned [BillingServiceInterface] has to have only one mock - * as well, otherwise, tests following the first one will try to interact with a different - * mock than [MapboxNavigationAccounts] singleton uses. - * - * To ensure that there's only one mock, we're storing it in a companion object. - */ - private val billingService = mockk(relaxed = true) - } - - @get:Rule - val coroutineRule = MainCoroutineRule() - - private val context: Context = mockk(relaxed = true) - private val applicationContext: Context = mockk(relaxed = true) - private val mapboxNavigation = mockk(relaxed = true) - private val navigationOptions: NavigationOptions = mockk(relaxed = true) - private val locationsCollector: LocationsCollector = mockk() - private val routeProgress = mockk() - private val originalRoute = mockk() - private val anotherRoute = mockk() - private val lastLocation = mockk() - private val originalRouteOptions = mockk() - private val anotherRouteOptions = mockk() - private val originalRouteLeg = mockk() - private val anotherRouteLeg = mockk() - private val originalRouteStep = mockk() - private val anotherRouteStep = mockk() - private val originalRouteSteps = listOf(originalRouteStep) - private val progressRouteSteps = listOf(anotherRouteStep) - private val originalRouteLegs = listOf(originalRouteLeg) - private val anotherRouteLegs = listOf(anotherRouteLeg) - private val originalStepManeuver = mockk() - private val anotherStepManeuver = mockk() - private val originalStepManeuverLocation = mockk() - private val anotherStepManeuverLocation = mockk() - private val legProgress = mockk() - private val stepProgress = mockk() - private val nextRouteLegProgress = mockk() - private val globalUserFeedbackCallback = mockk() - private val localUserFeedbackCallback = mockk() - - private val routeProgressObserverSlot = slot() - private val sessionStateObserverSlot = slot() - private val arrivalObserverSlot = slot() - private val routesObserverSlot = slot() - private val globalUserFeedbackSlot = slot() - private val localUserFeedbackSlot = slot() - - @Before - fun setup() { - mockkObject(BillingServiceProvider) - mockkStatic(TelemetrySystemUtils::obtainUniversalUniqueIdentifier) - mockkObject(EventsServiceProvider) - mockkObject(TelemetryServiceProvider) - mockkObject(TelemetryUtilsDelegate) - every { BillingServiceProvider.getInstance() } returns billingService - every { - mapboxNavigation.registerRouteProgressObserver(capture(routeProgressObserverSlot)) - } just runs - - every { - mapboxNavigation.registerNavigationSessionStateObserver( - capture(sessionStateObserverSlot) - ) - } just runs - - every { - mapboxNavigation.registerArrivalObserver(capture(arrivalObserverSlot)) - } just runs - - every { - mapboxNavigation.registerRoutesObserver(capture(routesObserverSlot)) - } just runs - - every { - globalUserFeedbackCallback.onNewUserFeedback(capture(globalUserFeedbackSlot)) - } just runs - - every { - localUserFeedbackCallback.onNewUserFeedback(capture(localUserFeedbackSlot)) - } just runs - - every { TelemetryUtilsDelegate.getEventsCollectionState() } returns true - every { TelemetryUtilsDelegate.setEventsCollectionState(any()) } just runs - } - - @After - fun cleanUp() { - unmockkObject(MapboxMetricsReporter) - unmockkStatic(TelemetrySystemUtils::obtainUniversalUniqueIdentifier) - unmockkObject(BillingServiceProvider) - unmockkObject(EventsServiceProvider) - unmockkObject(TelemetryServiceProvider) - unmockkObject(TelemetryUtilsDelegate) - } - - @Test - fun `telemetry idle before call initialize`() { - baseMock() - - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRouteProgress() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateRoute(originalRoute) - updateRouteProgress() - updateRoute(anotherRoute) - arrive() - - captureAndVerifyMetricsReporter(0) - } - - @Test - fun `telemetry identifier is retained`() { - baseMock() - - initTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRoute(originalRoute) - updateRouteProgress() - resetTelemetry() - - mockAnotherRoute() - initTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRoute(anotherRoute) - updateRouteProgress() - - val events = captureAndVerifyMetricsReporter(2) - checkIdentifiersDifferentNavSessions(events.subList(0, 1), events.subList(1, 2)) - checkEventsInSameSession(events.subList(0, 1)) - checkEventsInSameSession(events.subList(1, 2)) - } - - @Test - fun `telemetry idle after call destroy`() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - resetTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRouteProgress() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateSessionState(Idle) - updateRoute(originalRoute) - updateRouteProgress() - updateRoute(anotherRoute) - arrive() - - captureAndVerifyMetricsReporter(0) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun `route set before start session moved to ActiveGuidance`() { - baseMock() - - initTelemetry() - updateRoute(originalRoute) - updateRouteProgress() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRouteProgress() - postUserFeedback() - locationsCollector.flushBuffers() - updateRouteProgress() - arrive() - resetTelemetry() - - val events = captureAndVerifyMetricsReporter(3) - events.checkSequence( - NavigationDepartEvent::class, - NavigationFeedbackEvent::class, - NavigationArriveEvent::class, - ) - } - - @Test - fun turnstileEvent_sent_on_telemetry_init() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - - captureAndVerifyMetricsReporter(exactly = 0) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun turnstileEvent_populated_correctly() { - baseMock() - val expectedTurnstileEvent = TurnstileEvent( - UserSKUIdentifier.NAV2_SES_MAU, - "mock", - "mock" - ) - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - - assertEquals(1, turnstileEvents.size) - val actualEvent = turnstileEvents[0] - assertEquals(expectedTurnstileEvent.skuId, actualEvent.skuId) - } - - @Test - fun departEvent_sent_on_active_guidance_when_route_and_routeProgress_available() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(Idle) - updateRoute(originalRoute) - updateRoute(anotherRoute) - updateRoute(originalRoute) - - captureAndVerifyMetricsReporter(exactly = 0) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun active_guidance_events_are_not_sent_in_idle() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - - val events = captureAndVerifyMetricsReporter(exactly = 1) - assertTrue(events[0] is NavigationDepartEvent) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun departEvent_not_sent_without_route_and_routeProgress() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - - captureAndVerifyMetricsReporter(exactly = 0) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun departEvent_not_sent_without_route() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRouteProgress() - - captureAndVerifyMetricsReporter(exactly = 0) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun departEvent_not_sent_without_routeProgress() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRoute(originalRoute) - - captureAndVerifyMetricsReporter(exactly = 0) - assertEquals(1, turnstileEvents.size) - } - - @Test - fun cancelEvent_sent_on_active_guidance_stop() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - - val events = captureAndVerifyMetricsReporter(exactly = 3) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationCancelEvent) - assertTrue(events[2] is NavigationFreeDriveEvent) - assertEquals(3, events.size) - verify { locationsCollector.flushBuffers() } - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 2), events.subList(2, 3)) - } - - @Test - fun arriveEvent_sent_on_arrival() { - baseMock() - mockRouteProgress() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - arrive() - - val events = captureAndVerifyMetricsReporter(exactly = 2) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationArriveEvent) - assertEquals(2, events.size) - checkEventsInSameSession(events) - } - - @Test - fun cancel_and_depart_events_sent_on_external_route() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute) - updateRouteProgress() - - val events = captureAndVerifyMetricsReporter(exactly = 3) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationCancelEvent) - assertTrue(events[2] is NavigationDepartEvent) - assertEquals(3, events.size) - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 2), events.subList(2, 3)) - } - - @Test - fun depart_event_not_sent_on_external_route_without_route_progress() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute) - - val events = captureAndVerifyMetricsReporter(exactly = 2) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationCancelEvent) - assertEquals(2, events.size) - checkEventsInSameSession(events) - } - - @Test - fun depart_events_are_different_on_external_route() { - baseMock() - mockAnotherRoute() - val events = captureMetricsReporter() - - baseInitialization() - updateRoute(anotherRoute) - updateRouteProgress() - - val firstDepart = events[0] as NavigationDepartEvent - val secondDepart = events[2] as NavigationDepartEvent - assertNotSame(firstDepart.originalEstimatedDistance, secondDepart.originalEstimatedDistance) - assertNotSame(firstDepart.originalRequestIdentifier, secondDepart.originalRequestIdentifier) - } - - @Test - fun alternative_route() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_ALTERNATIVE) - updateRouteProgress() - - val events = captureAndVerifyMetricsReporter(exactly = 1) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - } - - @Test - fun refresh_route() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REFRESH) - updateRouteProgress() - - val events = captureAndVerifyMetricsReporter(exactly = 1) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - } - - @Test - fun clean_up_routes() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - routesObserverSlot.ifCaptured { - onRoutesChanged(createRoutesUpdatedResult(emptyList(), "")) - } - updateRouteProgress() - - val events = captureAndVerifyMetricsReporter(exactly = 1) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - } - - @Test - fun feedback_and_reroute_events_not_sent_on_arrival() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - postUserFeedback() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - postUserFeedback() - arrive() - - val events = captureAndVerifyMetricsReporter(exactly = 2) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationArriveEvent) - assertEquals(2, events.size) - checkEventsInSameSession(events) - } - - @Test - fun feedback_and_reroute_events_sent_on_free_drive() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - postUserFeedback() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - postUserFeedback() - postUserFeedback() - postUserFeedback() - postUserFeedback() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - postUserFeedback() - postUserFeedback() - resetTelemetry() - - val events = captureAndVerifyMetricsReporter(exactly = 11) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationFeedbackEvent) - assertTrue(events[2] is NavigationRerouteEvent) - assertTrue(events[3] is NavigationFeedbackEvent) - assertTrue(events[4] is NavigationFeedbackEvent) - assertTrue(events[5] is NavigationFeedbackEvent) - assertTrue(events[6] is NavigationFeedbackEvent) - assertTrue(events[7] is NavigationCancelEvent) - assertTrue(events[8] is NavigationFreeDriveEvent) - assertTrue(events[9] is NavigationFeedbackEvent) - assertTrue(events[10] is NavigationFeedbackEvent) - assertEquals(11, events.size) - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 8), events.subList(8, 11)) - } - - @Test - fun feedback_and_reroute_events_sent_on_idle_state() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - postUserFeedback() - postUserFeedback() - postUserFeedback() - postUserFeedback() - postUserFeedback() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - postUserFeedback() - updateSessionState(Idle) - - val events = captureAndVerifyMetricsReporter(exactly = 9) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationFeedbackEvent) - assertTrue(events[2] is NavigationFeedbackEvent) - assertTrue(events[3] is NavigationFeedbackEvent) - assertTrue(events[4] is NavigationFeedbackEvent) - assertTrue(events[5] is NavigationFeedbackEvent) - assertTrue(events[6] is NavigationRerouteEvent) - assertTrue(events[7] is NavigationFeedbackEvent) - assertTrue(events[8] is NavigationCancelEvent) - assertEquals(9, events.size) - checkEventsInSameSession(events) - } - - @Test - fun cache_feedback_send_on_session_stop() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - updateRouteProgress() - updateSessionState(Idle) - postUserFeedbackCached() - - val events = captureAndVerifyMetricsReporter(exactly = 4) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationRerouteEvent) - assertTrue(events[2] is NavigationCancelEvent) - assertTrue(events[3] is NavigationFeedbackEvent) - assertEquals(4, events.size) - } - - @Test - fun cache_feedback_post() { - val sessionIdentifier = "SESSION_IDENTIFIER" - val driverModeIdentifier = "DRIVER_MODE_IDENTIFIER" - val driverMode = FeedbackEvent.DRIVER_MODE_FREE_DRIVE - val driverModeStartTime = "DATE_TIME_FORMAT" - val rerouteCount = 1 - val mockLocationsBefore = arrayOf() - val mockLocationsAfter = arrayOf() - val locationEngine = "LOCATION_ENGINE_NAME_EXTERNAL" - val percentTimeInPortrait = 50 - val percentTimeInForeground = 20 - val eventVersion = 100 - val lastLocation = Point.fromLngLat(30.0, 40.0) - val phoneState = PhoneState( - volumeLevel = 5, - batteryLevel = 11, - screenBrightness = 16, - isBatteryPluggedIn = true, - connectivity = "CONNECTIVITY_STATE", - audioType = "AUDIO_TYPE", - applicationState = "APP_STATE", - created = "CREATED_DATA", - feedbackId = "FEEDBACK_ID", - userId = "USER_ID" - ) - val appMetadata = AppMetadata( - name = "APP_METADATA_NAME", - version = "APP_METADATA_VERSION", - userId = "APP_METADATA_USER_ID", - sessionId = "APP_METADATA_SESSION_ID", - ) - val cachedFeedbackMetadata = FeedbackMetadata( - sessionIdentifier = sessionIdentifier, - driverModeStartTime = driverModeStartTime, - driverModeIdentifier = driverModeIdentifier, - driverMode = driverMode, - rerouteCount = rerouteCount, - locationsBeforeEvent = mockLocationsBefore, - locationsAfterEvent = mockLocationsAfter, - locationEngineNameExternal = locationEngine, - percentTimeInPortrait = percentTimeInPortrait, - percentTimeInForeground = percentTimeInForeground, - eventVersion = eventVersion, - lastLocation = lastLocation, - phoneState = phoneState, - metricsDirectionsRoute = MetricsDirectionsRoute(route = null), - metricsRouteProgress = MetricsRouteProgress(routeProgress = null), - appMetadata = appMetadata, - ) - baseMock() - baseInitialization() - - postUserFeedbackCached(cachedFeedbackMetadata) - val events = captureAndVerifyMetricsReporter(exactly = 2) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationFeedbackEvent) - - val feedbackEvent = events[1] as NavigationFeedbackEvent - assertEquals(sessionIdentifier, feedbackEvent.navigatorSessionIdentifier) - assertEquals(driverModeIdentifier, feedbackEvent.sessionIdentifier) - assertEquals(driverMode, feedbackEvent.driverMode) - assertEquals(driverModeStartTime, feedbackEvent.startTimestamp) - assertEquals(rerouteCount, feedbackEvent.rerouteCount) - assertEquals(mockLocationsBefore, feedbackEvent.locationsBefore) - assertEquals(mockLocationsAfter, feedbackEvent.locationsAfter) - assertEquals(locationEngine, feedbackEvent.locationEngine) - assertEquals(percentTimeInPortrait, feedbackEvent.percentTimeInPortrait) - assertEquals(percentTimeInForeground, feedbackEvent.percentTimeInForeground) - assertEquals(eventVersion, feedbackEvent.eventVersion) - assertEquals(lastLocation.latitude(), feedbackEvent.lat) - assertEquals(lastLocation.longitude(), feedbackEvent.lng) - assertEquals(appMetadata, feedbackEvent.appMetadata) - } - - @Test - fun rerouteEvent_sent_on_offRoute() { - baseMock() - mockAnotherRoute() - mockRouteProgress() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - locationsCollector.flushBuffers() - - val events = captureAndVerifyMetricsReporter(exactly = 2) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationRerouteEvent) - assertEquals(2, events.size) - checkEventsInSameSession(events) - } - - @Test - fun rerouteEvent_accumulates_distance_traveled_on_offRoute() { - baseMock() - mockAnotherRoute() - mockRouteProgress() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - locationsCollector.flushBuffers() - every { routeProgress.currentState } returns RouteProgressState.OFF_ROUTE - updateRouteProgress(count = 1) - every { routeProgress.currentState } returns TRACKING - updateRouteProgress(count = 1) - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - locationsCollector.flushBuffers() - - val events = captureAndVerifyMetricsReporter(exactly = 3) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationRerouteEvent) - assertTrue(events[2] is NavigationRerouteEvent) - assertEquals( - (ROUTE_PROGRESS_DISTANCE_TRAVELED * 2).toInt(), - (events[2] as NavigationRerouteEvent).distanceCompleted - ) - } - - @Test - fun departEvent_populated_correctly() { - baseMock() - val events = captureMetricsReporter() - - baseInitialization() - - val departEvent = events[0] as NavigationDepartEvent - checkOriginalParams(departEvent, originalRoute) - assertEquals(0, departEvent.distanceCompleted) - } - - @Test - fun rerouteEvent_populated_correctly() { - baseMock() - mockAnotherRoute() - mockRouteProgress() - every { routeProgress.navigationRoute } returns anotherRoute - val events = captureMetricsReporter() - - baseInitialization() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - locationsCollector.flushBuffers() - - val rerouteEvent = events[1] as NavigationRerouteEvent - checkOriginalParams(rerouteEvent, anotherRoute) - assertEquals(routeProgress.distanceTraveled.toInt(), rerouteEvent.distanceCompleted) - } - - @Test - fun events_sent_correctly_on_multi_waypoints() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - nextWaypoint() - updateRouteProgress() - nextWaypoint() - updateRouteProgress() - nextWaypoint() - updateRouteProgress() - arrive() - - val events = captureAndVerifyMetricsReporter(exactly = 11) - assertEquals(1, turnstileEvents.size) - // origin - assertTrue(events[0] is NavigationDepartEvent) - // waypoint1 - assertTrue(events[1] is NavigationArriveEvent) - assertTrue(events[2] is NavigationCancelEvent) - assertTrue(events[3] is NavigationDepartEvent) - // waypoint2 - assertTrue(events[4] is NavigationArriveEvent) - assertTrue(events[5] is NavigationCancelEvent) - assertTrue(events[6] is NavigationDepartEvent) - // waypoint3 - assertTrue(events[7] is NavigationArriveEvent) - assertTrue(events[8] is NavigationCancelEvent) - assertTrue(events[9] is NavigationDepartEvent) - // destination - assertTrue(events[10] is NavigationArriveEvent) - - assertEquals(11, events.size) - checkEventsInSameSession(events) - } - - @Test - fun feedback_events_sent_correctly_on_multi_waypoints() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - postUserFeedback() - nextWaypoint() - updateRouteProgress() - mockFlushBuffers() - postUserFeedback() - postUserFeedback() - nextWaypoint() - updateRouteProgress() - arrive() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - - val events = captureAndVerifyMetricsReporter(exactly = 13) - assertEquals(1, turnstileEvents.size) - // origin - assertTrue(events[0] is NavigationDepartEvent) - // waypoint1 - assertTrue(events[1] is NavigationArriveEvent) - assertTrue(events[2] is NavigationFeedbackEvent) - assertTrue(events[3] is NavigationCancelEvent) - assertTrue(events[4] is NavigationDepartEvent) - // waypoint2 - assertTrue(events[5] is NavigationArriveEvent) - assertTrue(events[6] is NavigationFeedbackEvent) - assertTrue(events[7] is NavigationFeedbackEvent) - assertTrue(events[8] is NavigationCancelEvent) - assertTrue(events[9] is NavigationDepartEvent) - // destination - assertTrue(events[10] is NavigationArriveEvent) - assertTrue(events[11] is NavigationCancelEvent) - // free drive - assertTrue(events[12] is NavigationFreeDriveEvent) - - assertEquals(13, events.size) - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 12), events.subList(12, 13)) - } - - @Test - fun reroute_event_sent_correctly_on_multi_waypoints() { - baseMock() - mockAnotherRoute() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - nextWaypoint() - updateRouteProgress() - nextWaypoint() - updateRouteProgress() - mockFlushBuffers() - updateRoute(anotherRoute, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE) - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - - val events = captureAndVerifyMetricsReporter(exactly = 10) - assertEquals(1, turnstileEvents.size) - // origin - assertTrue(events[0] is NavigationDepartEvent) - // waypoint1 - assertTrue(events[1] is NavigationArriveEvent) - assertTrue(events[2] is NavigationCancelEvent) - assertTrue(events[3] is NavigationDepartEvent) - // waypoint2 - assertTrue(events[4] is NavigationArriveEvent) - assertTrue(events[5] is NavigationCancelEvent) - assertTrue(events[6] is NavigationDepartEvent) - // destination - assertTrue(events[7] is NavigationRerouteEvent) - assertTrue(events[8] is NavigationCancelEvent) - // free drive - assertTrue(events[9] is NavigationFreeDriveEvent) - - assertEquals(10, events.size) - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 9), events.subList(9, 10)) - } - - @Test - fun freeDrive_sent_when_state_changes_from_idle_to_free_drive() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - - val events = captureAndVerifyMetricsReporter(exactly = 1) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationFreeDriveEvent) - checkEventsInSameSession(events) - } - - @Test - fun freeDrive_sent_when_state_changes_from_active_guidance_to_free_drive() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - baseInitialization() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - - val events = captureAndVerifyMetricsReporter(exactly = 3) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationDepartEvent) - assertTrue(events[1] is NavigationCancelEvent) - assertTrue(events[2] is NavigationFreeDriveEvent) - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 2), events.subList(2, 3)) - } - - @Test - fun freeDrive_sent_when_state_changes_from_free_drive_to_active_guidance() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRoute(originalRoute) - updateRouteProgress() - - val events = captureAndVerifyMetricsReporter(exactly = 3) - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationFreeDriveEvent) // start free drive - assertTrue(events[1] is NavigationFreeDriveEvent) // stop free drive - assertTrue(events[2] is NavigationDepartEvent) - - checkEventsDividedBySessionsInSameNavSession(events.subList(0, 2), events.subList(2, 3)) - } - - @Test - fun freeDrive_sent_when_state_changes_from_free_drive_to_idle() { - baseMock() - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateSessionState(Idle) - - val events = captureAndVerifyMetricsReporter(exactly = 2) - - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationFreeDriveEvent) // start free drive - assertTrue(events[1] is NavigationFreeDriveEvent) // stop free drive - checkEventsInSameSession(events) - } - - @Test - fun freeDrive_sent_when_location_not_available() { - baseMock() - every { locationsCollector.lastLocation } returns null - val turnstileEvents = captureTurnstileEvents() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateSessionState(Idle) - - val events = captureAndVerifyMetricsReporter(exactly = 2) - - assertEquals(1, turnstileEvents.size) - assertTrue(events[0] is NavigationFreeDriveEvent) // start free drive - assertTrue(events[1] is NavigationFreeDriveEvent) // stop free drive - checkEventsInSameSession(events) - } - - @Test - fun freeDrive_sent_with_null_location_when_location_not_available_from_free_drive_to_idle() { - baseMock() - every { locationsCollector.lastLocation } returns null - val events = captureMetricsReporter() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - every { locationsCollector.lastLocation } returns lastLocation - updateSessionState(Idle) - - val freeDriveStart = events[0] as NavigationFreeDriveEvent - val freeDriveStop = events[1] as NavigationFreeDriveEvent - - assertEquals(null, freeDriveStart.location) - assertEquals(lastLocation.toTelemetryLocation(), freeDriveStop.location) - } - - @Test - fun freeDrive_sent_with_null_location_when_location_not_available_from_free_drive_to_active() { - baseMock() - every { locationsCollector.lastLocation } returns null - val events = captureMetricsReporter() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - every { locationsCollector.lastLocation } returns lastLocation - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - - val freeDriveStart = events[0] as NavigationFreeDriveEvent - val freeDriveStop = events[1] as NavigationFreeDriveEvent - - assertEquals(null, freeDriveStart.location) - assertEquals(lastLocation.toTelemetryLocation(), freeDriveStop.location) - } - - @Test - fun freeDrive_start_and_stop_sent_when_state_changes_from_free_drive_to_idle() { - baseMock() - val events = captureMetricsReporter() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateSessionState(Idle) - - val startFreeDriveEvent = events[0] as NavigationFreeDriveEvent - val stopFreeDriveEvent = events[1] as NavigationFreeDriveEvent - assertEquals(START.type, startFreeDriveEvent.eventType) - assertEquals(STOP.type, stopFreeDriveEvent.eventType) - } - - @Test - fun freeDrive_start_and_stop_sent_when_state_changes_from_free_drive_to_active_guidance() { - baseMock() - val events = captureMetricsReporter() - - initTelemetry() - updateSessionState(FreeDrive(FREE_DRIVE_SESSION_ID)) - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - - val startFreeDriveEvent = events[0] as NavigationFreeDriveEvent - val stopFreeDriveEvent = events[1] as NavigationFreeDriveEvent - assertEquals(START.type, startFreeDriveEvent.eventType) - assertEquals(STOP.type, stopFreeDriveEvent.eventType) - } - - @Test - fun onInit_registerRouteProgressObserver_called() { - baseMock() - - onInit { verify(exactly = 1) { mapboxNavigation.registerRouteProgressObserver(any()) } } - } - - @Test - fun onInit_registerLocationObserver_called() { - baseMock() - initMapboxMetricsReporter() - - onInit { verify(exactly = 1) { mapboxNavigation.registerLocationObserver(any()) } } - } - - @Test - fun onInit_registerRoutesObserver_called() { - baseMock() - - onInit { verify(exactly = 1) { mapboxNavigation.registerRoutesObserver(any()) } } - } - - @Test - fun onInit_registerNavigationSessionObserver_called() { - baseMock() - - onInit { - verify(exactly = 1) { mapboxNavigation.registerNavigationSessionStateObserver(any()) } - } - } - - @Test - fun onUnregisterListener_unregisterRouteProgressObserver_called() { - baseMock() - - onUnregister { - verify(exactly = 1) { mapboxNavigation.unregisterRouteProgressObserver(any()) } - } - } - - @Test - fun onUnregisterListener_unregisterLocationObserver_called() { - baseMock() - - onUnregister { verify(exactly = 1) { mapboxNavigation.unregisterLocationObserver(any()) } } - } - - @Test - fun onUnregisterListener_unregisterRoutesObserver_called() { - baseMock() - initMapboxMetricsReporter() - - onUnregister { verify(exactly = 1) { mapboxNavigation.unregisterRoutesObserver(any()) } } - } - - @Test - fun onUnregisterListener_unregisterNavigationSessionObserver_called() { - baseMock() - initMapboxMetricsReporter() - - onUnregister { - verify(exactly = 1) { mapboxNavigation.unregisterNavigationSessionStateObserver(any()) } - } - } - - @Test - fun after_unregister_onInit_registers_all_listeners_again() { - baseMock() - - initTelemetry() - resetTelemetry() - initTelemetry() - - verify(exactly = 2) { mapboxNavigation.registerRouteProgressObserver(any()) } - verify(exactly = 2) { mapboxNavigation.registerLocationObserver(any()) } - verify(exactly = 2) { mapboxNavigation.registerRoutesObserver(any()) } - verify(exactly = 0) { mapboxNavigation.registerOffRouteObserver(any()) } - verify(exactly = 2) { mapboxNavigation.registerNavigationSessionStateObserver(any()) } - - resetTelemetry() - } - - @Test - fun `user feedback observers are invoked for event without metadata`() { - baseMock() - baseInitialization() - mockLocationCollector() - val feedbackId = "feedback id" - every { TelemetrySystemUtils.obtainUniversalUniqueIdentifier() } returns feedbackId - - MapboxNavigationTelemetry.registerUserFeedbackCallback(globalUserFeedbackCallback) - postUserFeedback() - - verify(exactly = 1) { globalUserFeedbackCallback.onNewUserFeedback(any()) } - verify(exactly = 1) { localUserFeedbackCallback.onNewUserFeedback(any()) } - val userFeedback = globalUserFeedbackSlot.captured - assertEquals(feedbackId, userFeedback.feedbackId) - assertEquals(FEEDBACK_TYPE, userFeedback.feedbackType) - assertEquals(DESCRIPTION, userFeedback.description) - assertEquals(FEEDBACK_SOURCE, userFeedback.source) - assertEquals(SCREENSHOT, userFeedback.screenshot) - assertTrue(userFeedback.feedbackSubType.contentEquals(FEEDBACK_SUBTYPE)) - assertEquals(Point.fromLngLat(LAST_LOCATION_LON, LAST_LOCATION_LAT), userFeedback.location) - assertEquals(userFeedback, localUserFeedbackSlot.captured) - } - - @Test - fun `user feedback observers are invoked for event with metadata`() { - baseMock() - baseInitialization() - mockLocationCollector() - - MapboxNavigationTelemetry.registerUserFeedbackCallback(globalUserFeedbackCallback) - postUserFeedbackCached() - - verify(exactly = 1) { globalUserFeedbackCallback.onNewUserFeedback(any()) } - val userFeedback = globalUserFeedbackSlot.captured - assertEquals(DEFAULT_FEEDBACK_ID, userFeedback.feedbackId) - assertEquals(FEEDBACK_TYPE, userFeedback.feedbackType) - assertEquals(DESCRIPTION, userFeedback.description) - assertEquals(FEEDBACK_SOURCE, userFeedback.source) - assertEquals(SCREENSHOT, userFeedback.screenshot) - assertTrue(userFeedback.feedbackSubType.contentEquals(FEEDBACK_SUBTYPE)) - assertEquals( - Point.fromLngLat(ANOTHER_LAST_LOCATION_LON, ANOTHER_LAST_LOCATION_LAT), - userFeedback.location, - ) - assertEquals(userFeedback, localUserFeedbackSlot.captured) - } - - @Test - fun `user feedback observer is not invoked after unregistering`() { - baseMock() - baseInitialization() - - MapboxNavigationTelemetry.registerUserFeedbackCallback(globalUserFeedbackCallback) - MapboxNavigationTelemetry.unregisterUserFeedbackCallback(globalUserFeedbackCallback) - postUserFeedback() - - verify(exactly = 0) { globalUserFeedbackCallback.onNewUserFeedback(any()) } - } - - @Test - fun `custom event is dispatched when posted`() { - baseMock() - baseInitialization() - - postCustomEvent() - - val events = captureAndVerifyMetricsReporter(2) - events.checkSequence( - NavigationDepartEvent::class, - NavigationCustomEvent::class, - ) - } - - private fun baseInitialization() { - initTelemetry() - updateSessionState(ActiveGuidance(ACTIVE_GUIDANCE_SESSION_ID)) - updateRoute(originalRoute) - updateRouteProgress() - } - - private fun updateSessionState(state: NavigationSessionState) { - sessionStateObserverSlot.ifCaptured { - onNavigationSessionStateChanged(state) - } - } - - private fun updateRoute( - route: NavigationRoute, - @RoutesExtra.RoutesUpdateReason reason: String = - RoutesExtra.ROUTES_UPDATE_REASON_NEW, - ) { - routesObserverSlot.ifCaptured { - onRoutesChanged(createRoutesUpdatedResult(listOf(route), reason)) - } - } - - private fun updateRouteProgress(count: Int = 10) { - routeProgressObserverSlot.ifCaptured { - repeat(count) { - onRouteProgressChanged(routeProgress) - } - } - } - - private fun nextWaypoint() { - arrivalObserverSlot.captured.onNextRouteLegStart(nextRouteLegProgress) - // mock locationsCollector to do nothing - // because buffers will be empty after handleSessionCanceled on nextLeg - every { locationsCollector.flushBuffers() } just Runs - } - - private fun arrive() { - arrivalObserverSlot.ifCaptured { - onFinalDestinationArrival(routeProgress) - } - } - - private fun captureMetricsReporter(): List { - val events = mutableListOf() - every { MapboxMetricsReporter.addEvent(capture(events)) } just Runs - return events - } - - private fun captureAndVerifyMetricsReporter(exactly: Int): List { - val events = mutableListOf() - verify(exactly = exactly) { MapboxMetricsReporter.addEvent(capture(events)) } - return events - } - - private fun captureTurnstileEvents(): List { - val turnstileEvents = mutableListOf() - every { - MapboxMetricsReporter.sendTurnstileEvent(capture(turnstileEvents)) - } just runs - return turnstileEvents - } - - private fun baseMock() { - mockMetricsReporter() - mockContext() - mockTelemetryUtils() - - mockLocationCollector() - mockOriginalRoute() - mockRouteProgress() - } - - private fun mockOriginalRoute() { - every { originalRoute.directionsRoute.geometry() } returns ORIGINAL_ROUTE_GEOMETRY - every { originalRoute.directionsRoute.legs() } returns originalRouteLegs - every { originalRoute.directionsRoute.distance() } returns ORIGINAL_ROUTE_DISTANCE - every { originalRoute.directionsRoute.duration() } returns ORIGINAL_ROUTE_DURATION - every { originalRoute.directionsRoute.routeOptions() } returns originalRouteOptions - every { originalRoute.routeOptions } returns originalRouteOptions - every { originalRoute.routeIndex } returns ORIGINAL_ROUTE_ROUTE_INDEX - every { originalRouteOptions.profile() } returns ORIGINAL_ROUTE_OPTIONS_PROFILE - every { originalRouteOptions.geometries() } returns DirectionsCriteria.GEOMETRY_POLYLINE6 - every { originalRouteLeg.steps() } returns originalRouteSteps - every { originalRouteStep.maneuver() } returns originalStepManeuver - every { originalStepManeuver.location() } returns originalStepManeuverLocation - every { originalStepManeuverLocation.latitude() } returns - ORIGINAL_STEP_MANEUVER_LOCATION_LATITUDE - every { originalStepManeuverLocation.longitude() } returns - ORIGINAL_STEP_MANEUVER_LOCATION_LONGITUDE - every { originalRoute.directionsResponse.uuid() } returns - ORIGINAL_ROUTE_OPTIONS_REQUEST_UUID - } - - private fun mockAnotherRoute() { - every { anotherRoute.directionsRoute.geometry() } returns ANOTHER_ROUTE_GEOMETRY - every { anotherRoute.directionsRoute.distance() } returns ANOTHER_ROUTE_DISTANCE - every { anotherRoute.directionsRoute.duration() } returns ANOTHER_ROUTE_DURATION - every { anotherRoute.directionsRoute.legs() } returns anotherRouteLegs - every { anotherRoute.directionsRoute.routeOptions() } returns anotherRouteOptions - every { anotherRoute.routeIndex } returns ANOTHER_ROUTE_ROUTE_INDEX - every { anotherRoute.routeOptions } returns anotherRouteOptions - every { anotherRouteOptions.profile() } returns ANOTHER_ROUTE_OPTIONS_PROFILE - every { anotherRouteOptions.geometries() } returns DirectionsCriteria.GEOMETRY_POLYLINE6 - every { anotherRoute.directionsResponse.uuid() } returns ANOTHER_ROUTE_OPTIONS_REQUEST_UUID - every { anotherRouteLeg.steps() } returns progressRouteSteps - every { anotherRouteStep.maneuver() } returns anotherStepManeuver - every { anotherStepManeuver.location() } returns anotherStepManeuverLocation - every { anotherStepManeuverLocation.latitude() } returns - ANOTHER_STEP_MANEUVER_LOCATION_LATITUDE - every { anotherStepManeuverLocation.longitude() } returns - ANOTHER_STEP_MANEUVER_LOCATION_LONGITUDE - } - - private fun mockRouteProgress() { - every { routeProgress.navigationRoute } returns originalRoute - every { routeProgress.currentState } returns TRACKING - every { routeProgress.currentLegProgress } returns legProgress - every { routeProgress.distanceRemaining } returns ROUTE_PROGRESS_DISTANCE_REMAINING - every { routeProgress.durationRemaining } returns ROUTE_PROGRESS_DURATION_REMAINING - every { routeProgress.distanceTraveled } returns ROUTE_PROGRESS_DISTANCE_TRAVELED - every { legProgress.currentStepProgress } returns stepProgress - every { legProgress.upcomingStep } returns null - every { legProgress.legIndex } returns 0 - every { legProgress.routeLeg } returns null - every { stepProgress.stepIndex } returns STEP_INDEX - every { stepProgress.step } returns null - every { stepProgress.distanceRemaining } returns 0f - every { stepProgress.durationRemaining } returns 0.0 - } - - private fun mockMetricsReporter() { - initMapboxMetricsReporter() - mockkObject(MapboxMetricsReporter) - every { MapboxMetricsReporter.addEvent(any()) } just Runs - } - - private fun mockContext() { - every { navigationOptions.applicationContext } returns applicationContext - every { context.applicationContext } returns applicationContext - } - - private fun mockLocationCollector() { - every { locationsCollector.lastLocation } returns lastLocation - every { lastLocation.latitude } returns LAST_LOCATION_LAT - every { lastLocation.longitude } returns LAST_LOCATION_LON - every { lastLocation.speed } returns LAST_LOCATION_SPEED - every { lastLocation.bearing } returns LAST_LOCATION_BEARING - every { lastLocation.altitude } returns LAST_LOCATION_ALTITUDE - every { lastLocation.time } returns LAST_LOCATION_TIME - every { lastLocation.accuracy } returns LAST_LOCATION_ACCURACY - every { lastLocation.verticalAccuracyMeters } returns LAST_LOCATION_VERTICAL_ACCURACY - - mockFlushBuffers() - } - - private fun mockFlushBuffers() { - val onBufferFull = mutableListOf() - every { locationsCollector.collectLocations(capture(onBufferFull)) } just Runs - every { locationsCollector.flushBuffers() } answers { - onBufferFull.forEach { it.onBufferFull(listOf(), listOf()) } - onBufferFull.clear() - } - } - - private fun mockTelemetryUtils() { - val audioManager = mockk() - every { - applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager - } returns audioManager - every { audioManager.getStreamVolume(any()) } returns 1 - every { audioManager.getStreamMaxVolume(any()) } returns 2 - every { audioManager.isBluetoothScoOn } returns true - - val telephonyManager = mockk() - every { - applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - } returns telephonyManager - every { telephonyManager.dataNetworkType } returns 5 - every { telephonyManager.networkType } returns 6 - - val activityManager = mockk { - every { runningAppProcesses } returns listOf() - every { getRunningTasks(any()) } returns listOf() - every { getRunningServices(any()) } returns listOf() - } - every { - applicationContext.getSystemService(Context.ACTIVITY_SERVICE) - } returns activityManager - } - - private fun initTelemetry() { - MapboxNavigationTelemetry.initialize( - mapboxNavigation, - navigationOptions, - MapboxMetricsReporter, - locationsCollector, - ) - } - - private fun resetTelemetry() { - MapboxNavigationTelemetry.destroy(mapboxNavigation) - } - - private fun onInit(block: () -> Unit) { - initTelemetry() - block() - resetTelemetry() - } - - private fun onUnregister(block: () -> Unit) { - initTelemetry() - resetTelemetry() - block() - } - - private fun postCustomEvent() { - MapboxNavigationTelemetry.postCustomEvent( - "testPayload", - NavigationCustomEventType.ANALYTICS, - "1.2.3" - ) - } - - private fun postUserFeedback() { - MapboxNavigationTelemetry.postUserFeedback( - FEEDBACK_TYPE, - DESCRIPTION, - FEEDBACK_SOURCE, - SCREENSHOT, - FEEDBACK_SUBTYPE, - feedbackMetadata = null, - localUserFeedbackCallback, - ) - } - - private fun postUserFeedbackCached( - feedbackMetadata: FeedbackMetadata = FeedbackMetadata( - sessionIdentifier = "SESSION_ID", - eventVersion = 0, - phoneState = PhoneState( - 1, 2, 3, true, "connectivity", "audioType", - "appState", "01-01-2000", DEFAULT_FEEDBACK_ID, "6", - ), - metricsDirectionsRoute = MetricsDirectionsRoute(route = null), - metricsRouteProgress = MetricsRouteProgress(routeProgress = null), - lastLocation = Point.fromLngLat(ANOTHER_LAST_LOCATION_LON, ANOTHER_LAST_LOCATION_LAT), - ) - ) { - MapboxNavigationTelemetry.postUserFeedback( - FEEDBACK_TYPE, - DESCRIPTION, - FEEDBACK_SOURCE, - SCREENSHOT, - FEEDBACK_SUBTYPE, - feedbackMetadata, - localUserFeedbackCallback, - ) - } - - /** - * Inside MapboxNavigationTelemetry.initialize method we call postTurnstileEvent and build - * AppUserTurnstile. It checks a static context field inside MapboxTelemetry. - * To set that context field we need to init MapboxTelemetry. - * It is done inside MapboxMetricsReporter. - * After that method we mock MapboxMetricsReporter to use it in tests. - */ - private fun initMapboxMetricsReporter() { - every { - EventsServiceProvider.provideEventsService(any()) - } returns mockk(relaxUnitFun = true) - every { - TelemetryServiceProvider.provideTelemetryService(any()) - } returns mockk(relaxUnitFun = true) - val alarmManager = mockk() - every { - applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager - } returns alarmManager - every { context.applicationContext } returns applicationContext - - MapboxMetricsReporter.init(context, "pk.token", "userAgent") - } - - /** - * @param kClass must be TargetClass::class. Set [Nothing] to skip check for particular index - * @param skipTail if true [kClass] might size might be less than target List. - */ - private fun List.checkSequence( - vararg kClass: Any, - skipTail: Boolean = false - ) { - if (kClass.size > this.size) { - throw IllegalStateException( - "clazzes.size(=${kClass.size}) > this.size(=${this.size})" - ) - } - if (!skipTail && this.size != kClass.size) { - throw IllegalStateException( - "this.size(=${this.size}) must be equal to clazzes.size(=${kClass.size})" - ) - } - this.forEachIndexed { index, metricEvent -> - kClass.getOrNull(index)?.let { clazz -> - if (clazz != Nothing::class) { - assertEquals(metricEvent::class, clazz) - } - } - } - } - - private fun checkOriginalParams(event: NavigationEvent, currentRoute: NavigationRoute) { - assertEquals(SDK_IDENTIFIER, event.sdkIdentifier) - assertEquals(obtainStepCount(originalRoute.directionsRoute), event.originalStepCount) - assertEquals( - originalRoute.directionsRoute.distance().toInt(), - event.originalEstimatedDistance - ) - assertEquals( - originalRoute.directionsRoute.duration().toInt(), - event.originalEstimatedDuration - ) - assertEquals(originalRoute.directionsResponse.uuid(), event.originalRequestIdentifier) - assertEquals(originalRoute.directionsRoute.geometry(), event.originalGeometry) - assertEquals(locationsCollector.lastLocation?.latitude, event.lat) - assertEquals(locationsCollector.lastLocation?.longitude, event.lng) - assertEquals(false, event.simulation) - assertEquals(7, event.eventVersion) - - assertEquals( - routeProgress.currentLegProgress?.currentStepProgress?.stepIndex, - event.stepIndex - ) - assertEquals(routeProgress.distanceRemaining.toInt(), event.distanceRemaining) - assertEquals(routeProgress.durationRemaining.toInt(), event.durationRemaining) - assertEquals(currentRoute.directionsRoute.geometry(), event.geometry) - assertEquals(currentRoute.routeOptions.profile(), event.profile) - assertEquals(currentRoute.routeIndex.toInt(), event.legIndex) - assertEquals(obtainStepCount(currentRoute.directionsRoute), event.stepCount) - assertEquals(currentRoute.directionsRoute.legs()?.size, event.legCount) - - if (event is NavigationRerouteEvent) { - assertEquals( - anotherRoute.directionsRoute.distance().toInt(), - event.newDistanceRemaining - ) - assertEquals( - anotherRoute.directionsRoute.duration().toInt(), - event.newDurationRemaining - ) - assertEquals(anotherRoute.directionsRoute.geometry(), event.newGeometry) - assertEquals(1, event.rerouteCount) - } else { - assertEquals(0, event.rerouteCount) - } - - assertEquals( - obtainAbsoluteDistance( - lastLocation, - obtainRouteDestination(currentRoute.directionsRoute) - ), - event.absoluteDistanceToDestination - ) - assertEquals(currentRoute.directionsRoute.distance().toInt(), event.estimatedDistance) - assertEquals(currentRoute.directionsRoute.duration().toInt(), event.estimatedDuration) - assertEquals(obtainStepCount(currentRoute.directionsRoute), event.totalStepCount) - } - - /** - * Check that nav session identifiers the same for the same telemetry session and - * different for different telemetry sessions - */ - private fun checkIdentifiersDifferentNavSessions( - firstSessionEvents: List, - secondSessionEvents: List - ) { - fun List.toPair(): List> { - return this.mapNotNull { event -> - when (event) { - is NavigationEvent -> event.navigatorSessionIdentifier!! to event.toString() - is NavigationFreeDriveEvent -> - event.navigatorSessionIdentifier!! to event.toString() - else -> - throw IllegalArgumentException("Unknown event: ${event.javaClass.name}") - } - } - } - - val groupSessions = listOf(firstSessionEvents.toPair(), secondSessionEvents.toPair()) - .filter { it.isNotEmpty() } - - val sessionsIds = mutableListOf() - - groupSessions.forEach { sessionsElements -> - sessionsElements.reduce { acc, pair -> - assertEquals( - "navSessionIdentifier equals for all events under this session", - acc.first, - pair.first - ) - return@reduce pair - }.also { (navSessionId, _) -> - sessionsIds.add(navSessionId) - } - } - - if (sessionsIds.size > 1) { - assertNotSame( - sessionsIds[0], - sessionsIds[1] - ) - } - } - - private fun checkEventsDividedBySessionsInSameNavSession(vararg events: List) { - val reducedSessionEvents = events.map { checkEventsInSameSession(it) } - - reducedSessionEvents.reduce { acc, sessionEventCompareData -> - assertSame( - acc.navigatorSessionIdentifier, - sessionEventCompareData.navigatorSessionIdentifier - ) - return@reduce sessionEventCompareData - } - } - - private fun checkEventsInSameSession(events: List): SessionEventCompareData { - val compareData = events.asSessionEventCompareData() - - return compareData.reduce { acc, sessionEventCompareData -> - assertEquals( - acc.navigatorSessionIdentifier, - sessionEventCompareData.navigatorSessionIdentifier - ) - assertEquals( - acc.sessionIdentifier, - sessionEventCompareData.sessionIdentifier - ) - assertEquals( - acc.startTimestamp, - sessionEventCompareData.startTimestamp - ) - if ( - sessionEventCompareData.driverModeName != SessionEventCompareData.NO_DRIVER_MODE && - acc.driverModeName != SessionEventCompareData.NO_DRIVER_MODE - ) { - assertEquals(acc.driverModeName, sessionEventCompareData.driverModeName) - } - return@reduce sessionEventCompareData - } - } - - private fun List.asSessionEventCompareData(): List { - return this.mapNotNull { event -> - when (event) { - is NavigationEvent -> SessionEventCompareData( - event.navigatorSessionIdentifier!!, - event.sessionIdentifier!!, - event.driverMode!!, - event.startTimestamp!!, - event.toString() - ) - is NavigationFreeDriveEvent -> SessionEventCompareData( - event.navigatorSessionIdentifier!!, - event.sessionIdentifier!!, - SessionEventCompareData.NO_DRIVER_MODE, - event.startTimestamp!!, - event.toString() - ) - else -> throw IllegalArgumentException("Unknown event: ${event.javaClass.name}") - } - } - } - - private data class SessionEventCompareData( - val navigatorSessionIdentifier: String, - val sessionIdentifier: String, // driver mode id - val driverModeName: String = NO_DRIVER_MODE, // freedrive event doesn't have mode name - val startTimestamp: String, // driver mode start time - val metadata: String, // debug info: event.toString() - ) { - companion object { - const val NO_DRIVER_MODE = "NO_DRIVER_MODE" - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/NavigationFeedbackEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/NavigationFeedbackEventTest.kt deleted file mode 100644 index 3e1cd83f51a..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/NavigationFeedbackEventTest.kt +++ /dev/null @@ -1,208 +0,0 @@ -package com.mapbox.navigation.core.telemetry - -import com.google.gson.Gson -import com.mapbox.navigation.core.telemetry.events.AppMetadata -import com.mapbox.navigation.core.telemetry.events.MetricsRouteProgress -import com.mapbox.navigation.core.telemetry.events.NavigationFeedbackEvent -import com.mapbox.navigation.core.telemetry.events.NavigationStepData -import com.mapbox.navigation.core.telemetry.events.PhoneState -import com.mapbox.navigation.core.telemetry.events.TelemetryLocation -import io.mockk.every -import io.mockk.mockk -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test - -class NavigationFeedbackEventTest { - - private val gson = Gson() - private val phoneState = mockk() - private val metricsRouteProgress = mockk() - private val metadata = AppMetadata( - APP_METADATA_NAME, - APP_METADATA_VERSION, - APP_METADATA_USER_ID, - APP_METADATA_SESSION_ID - ) - - private val locationBefore = TelemetryLocation( - LOCATION_BEFORE_LATITUDE, - LOCATION_BEFORE_LONGITUDE, - LOCATION_BEFORE_SPEED, - LOCATION_BEFORE_BEARING, - LOCATION_BEFORE_ALTITUDE, - LOCATION_BEFORE_TIMESTAMP, - LOCATION_BEFORE_HORIZONTAL_ACCURACY, - LOCATION_BEFORE_VERTICAL_ACCURACY - ) - private val locationAfter = TelemetryLocation( - LOCATION_AFTER_LATITUDE, - LOCATION_AFTER_LONGITUDE, - LOCATION_AFTER_SPEED, - LOCATION_AFTER_BEARING, - LOCATION_AFTER_ALTITUDE, - LOCATION_AFTER_TIMESTAMP, - LOCATION_AFTER_HORIZONTAL_ACCURACY, - LOCATION_AFTER_VERTICAL_ACCURACY - ) - - @Before - fun setUp() { - every { phoneState.volumeLevel } returns STATE_VOLUME_LEVEL - every { phoneState.batteryLevel } returns STATE_BATTERY_LEVEL - every { phoneState.screenBrightness } returns STATE_SCREEN_BRIGHTNESS - every { phoneState.isBatteryPluggedIn } returns STATE_IS_BATTERY_PLUGGED_IN - every { phoneState.connectivity } returns STATE_CONNECTIVITY - every { phoneState.audioType } returns STATE_AUDIO_TYPE - every { phoneState.applicationState } returns STATE_APPLICATION_STATE - every { phoneState.created } returns STATE_CREATED - every { phoneState.feedbackId } returns STATE_FEEDBACK_ID - every { phoneState.userId } returns STATE_USER_ID - - every { - metricsRouteProgress.directionsRouteDistance - } returns PROGRESS_DIRECTIONS_ROUTE_DISTANCE - every { - metricsRouteProgress.directionsRouteDuration - } returns PROGRESS_DIRECTIONS_ROUTE_DURATION - every { - metricsRouteProgress.directionsRouteProfile - } returns PROGRESS_DIRECTIONS_ROUTE_PROFILE - every { metricsRouteProgress.distanceRemaining } returns PROGRESS_DISTANCE_REMAINING - every { metricsRouteProgress.durationRemaining } returns PROGRESS_DURATION_REMAINING - every { metricsRouteProgress.distanceTraveled } returns PROGRESS_DISTANCE_TRAVELED - every { metricsRouteProgress.currentStepDistance } returns PROGRESS_CURRENT_STEP_DISTANCE - every { metricsRouteProgress.currentStepDuration } returns PROGRESS_CURRENT_STEP_DURATION - every { - metricsRouteProgress.currentStepDistanceRemaining - } returns PROGRESS_CURRENT_STEP_DISTANCE_REMAINING - every { - metricsRouteProgress.currentStepDurationRemaining - } returns PROGRESS_CURRENT_STEP_DURATION_REMAINING - every { - metricsRouteProgress.upcomingStepInstruction - } returns PROGRESS_UPCOMING_STEP_INSTRUCTION - every { - metricsRouteProgress.upcomingStepModifier - } returns PROGRESS_UPCOMING_STEP_MODIFIER - every { metricsRouteProgress.upcomingStepType } returns PROGRESS_UPCOMING_STEP_TYPE - every { metricsRouteProgress.upcomingStepName } returns PROGRESS_UPCOMING_STEP_NAME - every { - metricsRouteProgress.previousStepInstruction - } returns PROGRESS_PREVIOUS_STEP_INSTRUCTION - every { metricsRouteProgress.previousStepModifier } returns PROGRESS_PREVIOUS_STEP_MODIFIER - every { metricsRouteProgress.previousStepType } returns PROGRESS_PREVIOUS_STEP_TYPE - every { metricsRouteProgress.previousStepName } returns PROGRESS_PREVIOUS_STEP_NAME - every { metricsRouteProgress.legIndex } returns PROGRESS_LEG_INDEX - every { metricsRouteProgress.legCount } returns PROGRESS_LEG_COUNT - every { metricsRouteProgress.stepIndex } returns PROGRESS_STEP_INDEX - every { metricsRouteProgress.stepCount } returns PROGRESS_STEP_COUNT - } - - @Test - fun checkSerialization() { - val feedbackEvent = - NavigationFeedbackEvent(phoneState, NavigationStepData(metricsRouteProgress)).apply { - feedbackType = EVENT_FEEDBACK_TYPE - source = EVENT_SOURCE - description = EVENT_DESCRIPTION - screenshot = EVENT_SCREENSHOT - appMetadata = metadata - feedbackSubType = arrayOf(EVENT_FEEDBACK_SUB_TYPE) - locationsBefore = arrayOf(locationBefore) - locationsAfter = arrayOf(locationAfter) - } - - val feedbackEventJson = gson.toJson(feedbackEvent) - val deserializedFeedbackEvent = - gson.fromJson(feedbackEventJson, NavigationFeedbackEvent::class.java) - - deserializedFeedbackEvent.run { - assertEquals(feedbackEvent.version, version) - assertEquals(feedbackEvent.userId, userId) - assertEquals(feedbackEvent.feedbackId, feedbackId) - assertEquals(feedbackEvent.step, step) - assertEquals(feedbackEvent.feedbackType, feedbackType) - assertEquals(feedbackEvent.source, source) - assertEquals(feedbackEvent.description, description) - assertEquals(feedbackEvent.screenshot, screenshot) - assertEquals(feedbackEvent.appMetadata, appMetadata) - assertArrayEquals(feedbackEvent.feedbackSubType, feedbackSubType) - assertArrayEquals(feedbackEvent.locationsBefore, locationsBefore) - assertArrayEquals(feedbackEvent.locationsAfter, locationsAfter) - } - } - - companion object { - // PhoneState - private const val STATE_VOLUME_LEVEL = 1 - private const val STATE_BATTERY_LEVEL = 2 - private const val STATE_SCREEN_BRIGHTNESS = 3 - private const val STATE_IS_BATTERY_PLUGGED_IN = true - private const val STATE_CONNECTIVITY = "connectivity" - private const val STATE_AUDIO_TYPE = "audioType" - private const val STATE_APPLICATION_STATE = "applicationState" - private const val STATE_CREATED = "created" - private const val STATE_FEEDBACK_ID = "feedbackId" - private const val STATE_USER_ID = "userId" - - // MetricsRouteProgress - private const val PROGRESS_DIRECTIONS_ROUTE_DISTANCE = 11 - private const val PROGRESS_DIRECTIONS_ROUTE_DURATION = 12 - private const val PROGRESS_DIRECTIONS_ROUTE_PROFILE = "directionsRouteProfile" - private const val PROGRESS_DISTANCE_REMAINING = 13 - private const val PROGRESS_DURATION_REMAINING = 14 - private const val PROGRESS_DISTANCE_TRAVELED = 15 - private const val PROGRESS_CURRENT_STEP_DISTANCE = 16 - private const val PROGRESS_CURRENT_STEP_DURATION = 17 - private const val PROGRESS_CURRENT_STEP_DISTANCE_REMAINING = 18 - private const val PROGRESS_CURRENT_STEP_DURATION_REMAINING = 19 - private const val PROGRESS_UPCOMING_STEP_INSTRUCTION = "upcomingStepInstruction" - private const val PROGRESS_UPCOMING_STEP_MODIFIER = "upcomingStepModifier" - private const val PROGRESS_UPCOMING_STEP_TYPE = "upcomingStepType" - private const val PROGRESS_UPCOMING_STEP_NAME = "upcomingStepName" - private const val PROGRESS_PREVIOUS_STEP_INSTRUCTION = "previousStepInstruction" - private const val PROGRESS_PREVIOUS_STEP_MODIFIER = "previousStepModifier" - private const val PROGRESS_PREVIOUS_STEP_TYPE = "previousStepType" - private const val PROGRESS_PREVIOUS_STEP_NAME = "previousStepName" - private const val PROGRESS_LEG_INDEX = 20 - private const val PROGRESS_LEG_COUNT = 21 - private const val PROGRESS_STEP_INDEX = 22 - private const val PROGRESS_STEP_COUNT = 23 - - // NavigationFeedbackEvent - private const val EVENT_VERSION = "version" - private const val EVENT_FEEDBACK_TYPE = "feedbackType" - private const val EVENT_SOURCE = "source" - private const val EVENT_DESCRIPTION = "description" - private const val EVENT_SCREENSHOT = "screenshot" - private const val EVENT_FEEDBACK_SUB_TYPE = "feedbackSubType" - - // AppMetadata - private const val APP_METADATA_NAME = "name" - private const val APP_METADATA_VERSION = "version" - private const val APP_METADATA_USER_ID = "userId" - private const val APP_METADATA_SESSION_ID = "sessionId" - - // FeedbackLocation before - private const val LOCATION_BEFORE_LATITUDE = 1.1 - private const val LOCATION_BEFORE_LONGITUDE = 2.2 - private const val LOCATION_BEFORE_SPEED = 30f - private const val LOCATION_BEFORE_BEARING = 200f - private const val LOCATION_BEFORE_ALTITUDE = 10.0 - private const val LOCATION_BEFORE_TIMESTAMP = "999999" - private const val LOCATION_BEFORE_HORIZONTAL_ACCURACY = 1f - private const val LOCATION_BEFORE_VERTICAL_ACCURACY = 2f - - // FeedbackLocation after - private const val LOCATION_AFTER_LATITUDE = 22.1 - private const val LOCATION_AFTER_LONGITUDE = 33.2 - private const val LOCATION_AFTER_SPEED = 50f - private const val LOCATION_AFTER_BEARING = 330f - private const val LOCATION_AFTER_ALTITUDE = 17.0 - private const val LOCATION_AFTER_TIMESTAMP = "55555555" - private const val LOCATION_AFTER_HORIZONTAL_ACCURACY = 55f - private const val LOCATION_AFTER_VERTICAL_ACCURACY = 44f - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/CoreTelemetryEventUtilsTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/CoreTelemetryEventUtilsTest.kt deleted file mode 100644 index 556d5536aeb..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/CoreTelemetryEventUtilsTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyTelemetryLocations -import com.mapbox.navigation.core.testutil.EventsProvider -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Test - -class CoreTelemetryEventUtilsTest { - - @Test - fun `TelemetryLocation to value`() { - val telemetryLocation = TelemetryLocation( - latitude = 1.1, - longitude = 2.2, - speed = 3.3f, - bearing = 4.4f, - altitude = 5.5, - timestamp = "timestamp_0", - horizontalAccuracy = 6.6f, - verticalAccuracy = 7.7f, - ) - - val toValue = telemetryLocation.toValue() - - (toValue.contents as HashMap).let { content -> - assertEquals(1.1, content["lat"]!!.contents as Double, 0.000001) - assertEquals(2.2, content["lng"]!!.contents as Double, 0.000001) - assertEquals(3.3, content["speed"]!!.contents as Double, 0.001) - assertEquals(4.4, content["course"]!!.contents as Double, 0.001) - assertEquals(5.5, content["altitude"]!!.contents as Double, 0.001) - assertEquals("timestamp_0", content["timestamp"]!!.contents) - assertEquals(6.6, content["horizontalAccuracy"]!!.contents as Double, 0.001) - assertEquals(7.7, content["verticalAccuracy"]!!.contents as Double, 0.001) - } - } - - @Test - fun `AppMetadata to value`() { - val appMetadata = AppMetadata( - name = "name_0", - version = "version_0", - userId = "userId_0", - sessionId = "sessionId_0", - ) - - val toValue = appMetadata.toValue() - - (toValue.contents as HashMap).let { content -> - assertEquals("name_0", content["name"]!!.contents) - assertEquals("version_0", content["version"]!!.contents) - assertEquals("userId_0", content["userId"]!!.contents) - assertEquals("sessionId_0", content["sessionId"]!!.contents) - } - } - - @Test - fun `NavigationStepData to value`() { - val mockNavigationStepData = EventsProvider.mockNavigationStepData() - - val toValue = mockNavigationStepData.toValue() - - (toValue.contents as HashMap).let { content -> - assertEquals(1L, content["durationRemaining"]!!.contents) - assertEquals(2L, content["distance"]!!.contents) - assertEquals(3L, content["distanceRemaining"]!!.contents) - assertEquals(4L, content["duration"]!!.contents) - assertEquals("upcomingName_0", content["upcomingName"]!!.contents) - assertEquals("upcomingModifier_0", content["upcomingModifier"]!!.contents) - assertEquals("previousInstruction_0", content["previousInstruction"]!!.contents) - assertEquals("previousName_0", content["previousName"]!!.contents) - assertEquals("upcomingInstruction_0", content["upcomingInstruction"]!!.contents) - assertEquals("previousType_0", content["previousType"]!!.contents) - assertEquals("upcomingType_0", content["upcomingType"]!!.contents) - assertEquals("previousModifier_0", content["previousModifier"]!!.contents) - } - } - - @Test - fun `Array of strings to value`() { - val strings = arrayOf( - "string_0", - "string_1", - "string_2", - ) - - val toValue = strings.toValue { toValue() } - - assertArrayEquals( - strings, - (toValue.contents!! as List).map { it.contents as String }.toTypedArray() - ) - } - - @Test - fun `Array of TelemetryLocation to value`() { - val telemetryLocations = EventsProvider.provideDefaultTelemetryLocationsArray() - - val toValue = telemetryLocations.toValue { toValue() } - - toValue.verifyTelemetryLocations(telemetryLocations) - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/EventsTestHelper.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/EventsTestHelper.kt deleted file mode 100644 index 0aae454f6a1..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/EventsTestHelper.kt +++ /dev/null @@ -1,451 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import io.mockk.every -import io.mockk.mockk -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue - -internal object EventsTestHelper { - - // PhoneState - const val VOLUME_LEVEL = 10 - const val BATTERY_LEVEL = 11 - const val SCREEN_BRIGHTNESS = 12 - const val IS_BATTERY_PLUGGEDIN = false - const val CONNECTIVITY = "connectivity_0" - const val AUDIO_TYPE = "audioType_0" - const val APPLICATION_STATE = "applicationState_0" - const val CREATED = "created_0" - const val FEEDBACK_ID = "feedbackId_0" - const val USER_ID = "userId_0" - - // NavigationEvent - const val NAVIGATOR_SESSION_IDENTIFIER = "navigatorSessionIdentifier_0" - const val START_TIMESTAMP = "123456789" - const val DRIVER_MODE = "driverMode" - const val SESSION_IDENTIFIER = "sessionIdentifier_0" - const val GEOMETRY = "geometry_0" - const val PROFILE = "profile_0" - const val REQUEST_IDENTIFIER = "requestIdentifier_0" - const val ORIGINAL_GEOMETRY = "originalGeometry_0" - const val LOCATION_ENGINE = "locationEngine_0" - const val TRIP_IDENTIFIER = "tripIdentifier_0" - const val LAT = 1.1 - const val LNG = 2.2 - const val SIMULATION = true - const val ABSOLUTE_DISTANCE_TO_DESTINATION = 100 - const val PERCENT_TIME_IN_PORTRAIT = 20 - const val PERCENT_TIME_IN_FOREGROUND = 40 - const val DISTANCE_COMPLETED = 1000 - const val DISTANCE_REMAINING = 1001 - const val EVENT_VERSION = 1002 - const val ESTIMATED_DISTANCE = 1003 - const val ESTIMATED_DURATION = 1004 - const val REROUTE_COUNT = 1005 - const val ORIGINAL_ESTIMATED_DISTANCE = 1006 - const val ORIGINAL_ESTIMATED_DURATION = 1007 - const val STEP_COUNT = 1008 - const val ORIGINAL_STEP_COUNT = 1009 - const val LEG_INDEX = 1010 - const val LEG_COUNT = 1011 - const val STEP_INDEX = 1012 - const val VOICE_INDEX = 1013 - const val BANNER_INDEX = 1014 - const val TOTAL_STEP_COUNT = 1015 - val APP_METADATA = AppMetadata( - name = "app_metadata_name", - version = "app_metadata_version", - userId = "app_metadata_userId", - sessionId = "app_metadata_sessionId", - ) - - fun mockPhoneState( - volumeLevel: Int = VOLUME_LEVEL, - batteryLevel: Int = BATTERY_LEVEL, - screenBrightness: Int = SCREEN_BRIGHTNESS, - isBatteryPluggedIn: Boolean = IS_BATTERY_PLUGGEDIN, - connectivity: String = CONNECTIVITY, - audioType: String = AUDIO_TYPE, - applicationState: String = APPLICATION_STATE, - created: String = CREATED, - feedbackId: String = FEEDBACK_ID, - userId: String = USER_ID, - ): PhoneState = - mockk { - every { this@mockk.volumeLevel } returns volumeLevel - every { this@mockk.batteryLevel } returns batteryLevel - every { this@mockk.screenBrightness } returns screenBrightness - every { this@mockk.isBatteryPluggedIn } returns isBatteryPluggedIn - every { this@mockk.connectivity } returns connectivity - every { this@mockk.audioType } returns audioType - every { this@mockk.applicationState } returns applicationState - every { this@mockk.created } returns created - every { this@mockk.feedbackId } returns feedbackId - every { this@mockk.userId } returns userId - } - - fun NavigationEvent.fillValues( - navigatorSessionIdentifier: String = NAVIGATOR_SESSION_IDENTIFIER, - startTimestamp: String = START_TIMESTAMP, - driverMode: String = DRIVER_MODE, - sessionIdentifier: String = SESSION_IDENTIFIER, - geometry: String = GEOMETRY, - profile: String = PROFILE, - requestIdentifier: String = REQUEST_IDENTIFIER, - originalGeometry: String = ORIGINAL_GEOMETRY, - locationEngine: String = LOCATION_ENGINE, - tripIdentifier: String = TRIP_IDENTIFIER, - lat: Double = LAT, - lng: Double = LNG, - simulation: Boolean = SIMULATION, - absoluteDistanceToDestination: Int = ABSOLUTE_DISTANCE_TO_DESTINATION, - percentTimeInPortrait: Int = PERCENT_TIME_IN_PORTRAIT, - percentTimeInForeground: Int = PERCENT_TIME_IN_FOREGROUND, - distanceCompleted: Int = DISTANCE_COMPLETED, - distanceRemaining: Int = DISTANCE_REMAINING, - eventVersion: Int = EVENT_VERSION, - estimatedDistance: Int = ESTIMATED_DISTANCE, - estimatedDuration: Int = ESTIMATED_DURATION, - rerouteCount: Int = REROUTE_COUNT, - originalEstimatedDistance: Int = ORIGINAL_ESTIMATED_DISTANCE, - originalEstimatedDuration: Int = ORIGINAL_ESTIMATED_DURATION, - stepCount: Int = STEP_COUNT, - originalStepCount: Int = ORIGINAL_STEP_COUNT, - legIndex: Int = LEG_INDEX, - legCount: Int = LEG_COUNT, - stepIndex: Int = STEP_INDEX, - voiceIndex: Int = VOICE_INDEX, - bannerIndex: Int = BANNER_INDEX, - totalStepCount: Int = TOTAL_STEP_COUNT, - appMetadata: AppMetadata = APP_METADATA, - ) { - this.navigatorSessionIdentifier = navigatorSessionIdentifier - this.startTimestamp = startTimestamp - this.driverMode = driverMode - this.sessionIdentifier = sessionIdentifier - this.geometry = geometry - this.profile = profile - this.requestIdentifier = requestIdentifier - this.originalGeometry = originalGeometry - this.locationEngine = locationEngine - this.tripIdentifier = tripIdentifier - this.lat = lat - this.lng = lng - this.simulation = simulation - this.absoluteDistanceToDestination = absoluteDistanceToDestination - this.percentTimeInPortrait = percentTimeInPortrait - this.percentTimeInForeground = percentTimeInForeground - this.distanceCompleted = distanceCompleted - this.distanceRemaining = distanceRemaining - this.eventVersion = eventVersion - this.estimatedDistance = estimatedDistance - this.estimatedDuration = estimatedDuration - this.rerouteCount = rerouteCount - this.originalEstimatedDistance = originalEstimatedDistance - this.originalEstimatedDuration = originalEstimatedDuration - this.stepCount = stepCount - this.originalStepCount = originalStepCount - this.legIndex = legIndex - this.legCount = legCount - this.stepIndex = stepIndex - this.voiceIndex = voiceIndex - this.bannerIndex = bannerIndex - this.totalStepCount = totalStepCount - this.appMetadata = appMetadata - } - - fun Value.verifyNavigationEventFields( - eventName: String, - navigatorSessionIdentifier: String = NAVIGATOR_SESSION_IDENTIFIER, - startTimestamp: String = START_TIMESTAMP, - driverMode: String = DRIVER_MODE, - sessionIdentifier: String = SESSION_IDENTIFIER, - geometry: String = GEOMETRY, - profile: String = PROFILE, - requestIdentifier: String = REQUEST_IDENTIFIER, - originalGeometry: String = ORIGINAL_GEOMETRY, - locationEngine: String = LOCATION_ENGINE, - tripIdentifier: String = TRIP_IDENTIFIER, - lat: Double = LAT, - lng: Double = LNG, - simulation: Boolean = SIMULATION, - absoluteDistanceToDestination: Int = ABSOLUTE_DISTANCE_TO_DESTINATION, - percentTimeInPortrait: Int = PERCENT_TIME_IN_PORTRAIT, - percentTimeInForeground: Int = PERCENT_TIME_IN_FOREGROUND, - distanceCompleted: Int = DISTANCE_COMPLETED, - distanceRemaining: Int = DISTANCE_REMAINING, - eventVersion: Int = EVENT_VERSION, - estimatedDistance: Int = ESTIMATED_DISTANCE, - estimatedDuration: Int = ESTIMATED_DURATION, - rerouteCount: Int = REROUTE_COUNT, - originalEstimatedDistance: Int = ORIGINAL_ESTIMATED_DISTANCE, - originalEstimatedDuration: Int = ORIGINAL_ESTIMATED_DURATION, - stepCount: Int = STEP_COUNT, - originalStepCount: Int = ORIGINAL_STEP_COUNT, - legIndex: Int = LEG_INDEX, - legCount: Int = LEG_COUNT, - stepIndex: Int = STEP_INDEX, - voiceIndex: Int = VOICE_INDEX, - bannerIndex: Int = BANNER_INDEX, - totalStepCount: Int = TOTAL_STEP_COUNT, - appMetadata: AppMetadata = APP_METADATA, - ) { - (this.contents as Map).let { content -> - assertEquals( - "event value", - eventName, - content["event"]!!.contents - ) - assertEquals( - "navigatorSessionIdentifier value", - navigatorSessionIdentifier, - content["navigatorSessionIdentifier"]!!.contents - ) - assertEquals( - "startTimestamp value", - startTimestamp, - content["startTimestamp"]!!.contents - ) - assertEquals( - "driverMode value", - driverMode, - content["driverMode"]!!.contents - ) - assertEquals( - "sessionIdentifier value", - sessionIdentifier, - content["sessionIdentifier"]!!.contents - ) - assertEquals( - "geometry value", - geometry, - content["geometry"]!!.contents - ) - assertEquals( - "profile value", - profile, - content["profile"]!!.contents - ) - assertEquals( - "requestIdentifier value", - requestIdentifier, - content["requestIdentifier"]!!.contents - ) - assertEquals( - "originalGeometry value", - originalGeometry, - content["originalGeometry"]!!.contents - ) - assertEquals( - "locationEngine value", - locationEngine, - content["locationEngine"]!!.contents - ) - assertEquals( - "tripIdentifier value", - tripIdentifier, - content["tripIdentifier"]!!.contents - ) - assertEquals( - "lat value", - lat, - content["lat"]!!.contents as Double, - 0.0 - ) - assertEquals( - "lng value", - lng, - content["lng"]!!.contents as Double, - 0.0 - ) - assertEquals( - "simulation value", - simulation, - content["simulation"]!!.contents - ) - assertEquals( - "absoluteDistanceToDestination value", - absoluteDistanceToDestination.toLong(), - content["absoluteDistanceToDestination"]!!.contents - ) - assertEquals( - "percentTimeInPortrait value", - percentTimeInPortrait.toLong(), - content["percentTimeInPortrait"]!!.contents - ) - assertEquals( - "percentTimeInForeground value", - percentTimeInForeground.toLong(), - content["percentTimeInForeground"]!!.contents - ) - assertEquals( - "distanceCompleted value", - distanceCompleted.toLong(), - content["distanceCompleted"]!!.contents - ) - assertEquals( - "distanceRemaining value", - distanceRemaining.toLong(), - content["distanceRemaining"]!!.contents - ) - assertEquals( - "eventVersion value", - eventVersion.toLong(), - content["eventVersion"]!!.contents - ) - assertEquals( - "estimatedDistance value", - estimatedDistance.toLong(), - content["estimatedDistance"]!!.contents - ) - assertEquals( - "estimatedDuration value", - estimatedDuration.toLong(), - content["estimatedDuration"]!!.contents - ) - assertEquals( - "rerouteCount value", - rerouteCount.toLong(), - content["rerouteCount"]!!.contents - ) - assertEquals( - "originalEstimatedDistance value", - originalEstimatedDistance.toLong(), - content["originalEstimatedDistance"]!!.contents - ) - assertEquals( - "originalEstimatedDuration value", - originalEstimatedDuration.toLong(), - content["originalEstimatedDuration"]!!.contents - ) - assertEquals( - "stepCount value", - stepCount.toLong(), - content["stepCount"]!!.contents - ) - assertEquals( - "originalStepCount value", - originalStepCount.toLong(), - content["originalStepCount"]!!.contents - ) - assertEquals( - "legIndex value", - legIndex.toLong(), - content["legIndex"]!!.contents - ) - assertEquals( - "legCount value", - legCount.toLong(), - content["legCount"]!!.contents - ) - assertEquals( - "stepIndex value", - stepIndex.toLong(), - content["stepIndex"]!!.contents - ) - assertEquals( - "voiceIndex value", - voiceIndex.toLong(), - content["voiceIndex"]!!.contents - ) - assertEquals( - "bannerIndex value", - bannerIndex.toLong(), - content["bannerIndex"]!!.contents - ) - assertEquals( - "totalStepCount value", - totalStepCount.toLong(), - content["totalStepCount"]!!.contents - ) - assertTrue(content.containsKey("appMetadata")) - content["appMetadata"]!!.verifyAppMetadata(appMetadata) - } - } - - fun Value.verifyAppMetadata(appMetadata: AppMetadata) { - (contents as Map).let { appMetadataContent -> - assertEquals( - "appMetadata, name value", - appMetadata.name, - appMetadataContent["name"]!!.contents - ) - assertEquals( - "appMetadata, version value", - appMetadata.version, - appMetadataContent["version"]!!.contents - ) - assertEquals( - "appMetadata, sessionId value", - appMetadata.sessionId, - appMetadataContent["sessionId"]!!.contents - ) - assertEquals( - "appMetadata, userId value", - appMetadata.userId, - appMetadataContent["userId"]!!.contents - ) - } - } - - fun Value.verifyTelemetryLocation(location: TelemetryLocation) { - (contents as Map).let { content -> - assertEquals( - "check TelemetryLocation: latitude value", - location.latitude, - content["lat"]!!.contents as Double, - 0.000001 - ) - assertEquals( - "check TelemetryLocation: longitude value", - location.longitude, - content["lng"]!!.contents as Double, - 0.000001 - ) - assertEquals( - "check TelemetryLocation: speed value", - location.speed.toString().toDouble(), - content["speed"]!!.contents as Double, - 0.001 - ) - assertEquals( - "check TelemetryLocation: bearing value", - location.bearing.toString().toDouble(), - content["course"]!!.contents as Double, - 0.001 - ) - assertEquals( - "check TelemetryLocation: altitude value", - location.altitude, - content["altitude"]!!.contents - ) - assertEquals( - "check TelemetryLocation: timestamp value", - location.timestamp, - content["timestamp"]!!.contents - ) - assertEquals( - "check TelemetryLocation: timestamp value", - location.horizontalAccuracy.toString().toDouble(), - content["horizontalAccuracy"]!!.contents as Double, - 0.001 - ) - assertEquals( - "check TelemetryLocation: verticalAccuracy value", - location.verticalAccuracy.toString().toDouble(), - content["verticalAccuracy"]!!.contents as Double, - 0.001 - ) - } - } - - fun Value.verifyTelemetryLocations(array: Array) { - (contents!! as List).let { content -> - assertEquals(array.size, content.size) - content.forEachIndexed { index, value -> - value.verifyTelemetryLocation(array[index]) - } - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataTest.kt deleted file mode 100644 index e78264ecd54..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataTest.kt +++ /dev/null @@ -1,115 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import android.location.Location -import com.google.gson.Gson -import com.google.gson.JsonParser -import com.mapbox.geojson.Point -import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI -import com.mapbox.navigation.core.internal.telemetry.toTelemetryLocations -import com.mapbox.navigation.testing.FileUtils -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Test - -@ExperimentalPreviewMapboxNavigationAPI -class FeedbackMetadataTest { - - private val jsonFeedbackMetadata = FileUtils.loadJsonFixture("feedback_metadata.json") - - private companion object { - const val SESSION_IDENTIFIER = "SESSION_IDENTIFIER" - const val DRIVER_MODE_IDENTIFIER = "DRIVER_MODE_IDENTIFIER" - const val DRIVER_MODE = FeedbackEvent.DRIVER_MODE_TRIP - const val DRIVER_MODE_START_TIME = "DATE_TIME_FORMAT" - const val REROUTE_COUNT = 1 - - private val locationsBefore = listOf( - Location("providername1").apply { - latitude = 0.1 - longitude = 0.2 - }, - Location("providername2").apply { - latitude = 1.1 - longitude = 1.2 - } - ) - private val locationsAfter = listOf( - Location("providername3").apply { - latitude = 3.1 - longitude = 3.2 - }, - Location("providername4").apply { - latitude = 4.1 - longitude = 4.2 - } - ) - } - - @Test - fun sanity() { - assertNotNull(jsonFeedbackMetadata) - assertTrue(jsonFeedbackMetadata.isNotBlank()) - } - - @Test - fun feedbackMetadataToJson() { - val feedbackMetadataOriginal = provideFeedbackMetadata() - - val json = feedbackMetadataOriginal.toJson(Gson()) - val feedbackMetadataFromJson = FeedbackMetadata.fromJson(json) - - assertNotNull(feedbackMetadataFromJson) - assertEquals(feedbackMetadataOriginal, feedbackMetadataFromJson) - } - - @Test - fun feedbackMetadataFromJson() { - val feedbackMetadataFromJson = FeedbackMetadata.fromJson(jsonFeedbackMetadata) - - val newJsonFeedbackMetadata = feedbackMetadataFromJson?.toJson(Gson()) - - assertNotNull(feedbackMetadataFromJson) - assertNotNull(newJsonFeedbackMetadata) - assertEquals( - JsonParser.parseString(jsonFeedbackMetadata), - JsonParser.parseString(newJsonFeedbackMetadata), - ) - } - - private fun provideFeedbackMetadata(): FeedbackMetadata = - FeedbackMetadata( - sessionIdentifier = SESSION_IDENTIFIER, - driverModeStartTime = DRIVER_MODE_START_TIME, - driverModeIdentifier = DRIVER_MODE_IDENTIFIER, - driverMode = DRIVER_MODE, - rerouteCount = REROUTE_COUNT, - locationsBeforeEvent = locationsBefore.toTelemetryLocations(), - locationsAfterEvent = locationsAfter.toTelemetryLocations(), - locationEngineNameExternal = "LOCATION_ENGINE_NAME_EXTERNAL", - percentTimeInPortrait = 50, - percentTimeInForeground = 20, - eventVersion = 100, - lastLocation = Point.fromLngLat(30.0, 40.0), - phoneState = PhoneState( - volumeLevel = 5, - batteryLevel = 11, - screenBrightness = 16, - isBatteryPluggedIn = true, - connectivity = "CONNECTIVITY_STATE", - audioType = "AUDIO_TYPE", - applicationState = "APP_STATE", - created = "CREATED_DATA", - feedbackId = "FEEDBACK_ID", - userId = "USER_ID" - ), - metricsDirectionsRoute = MetricsDirectionsRoute(route = null), - metricsRouteProgress = MetricsRouteProgress(routeProgress = null), - appMetadata = AppMetadata( - name = "APP_METADATA_NAME", - version = "APP_METADATA_VERSION", - userId = "APP_METADATA_USER_ID", - sessionId = "APP_METADATA_SESSION_ID", - ) - ) -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataWrapperTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataWrapperTest.kt index 1092c51aca5..8dcb497a69e 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataWrapperTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/FeedbackMetadataWrapperTest.kt @@ -1,160 +1,33 @@ package com.mapbox.navigation.core.telemetry.events -import android.location.Location -import com.mapbox.geojson.Point import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI -import com.mapbox.navigation.core.internal.telemetry.toTelemetryLocations -import com.mapbox.navigation.core.telemetry.LocationsCollector +import com.mapbox.navigator.UserFeedbackHandle +import com.mapbox.navigator.UserFeedbackMetadata import io.mockk.every -import io.mockk.just import io.mockk.mockk -import io.mockk.runs -import io.mockk.slot import io.mockk.verify -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Before +import junit.framework.Assert.assertNotNull import org.junit.Test @ExperimentalPreviewMapboxNavigationAPI class FeedbackMetadataWrapperTest { - private lateinit var wrapper: FeedbackMetadataWrapper - private lateinit var phoneState: PhoneState - private lateinit var locationsCollector: LocationsCollector - private val locationsCollectorListenerSlot = - slot() - - private companion object { - const val SESSION_IDENTIFIER = "SESSION_IDENTIFIER" - const val DRIVER_MODE_IDENTIFIER = "DRIVER_MODE_IDENTIFIER" - const val DRIVER_MODE = FeedbackEvent.DRIVER_MODE_TRIP - const val DRIVER_MODE_START_TIME = "DATE_TIME" - const val REROUTE_COUNT = 1 - - private val locationsBefore = listOf( - Location("").apply { - latitude = 0.1 - longitude = 0.2 - }, - Location("").apply { - latitude = 1.1 - longitude = 1.2 - } - ) - private val locationsAfter = listOf( - Location("").apply { - latitude = 3.1 - longitude = 3.2 - }, - Location("").apply { - latitude = 4.1 - longitude = 4.2 - } - ) - } - - @Before - fun setup() { - locationsCollector = mockk(relaxUnitFun = true) { - every { collectLocations(capture(locationsCollectorListenerSlot)) } just runs - } - - phoneState = PhoneState( - volumeLevel = 5, - batteryLevel = 11, - screenBrightness = 16, - isBatteryPluggedIn = true, - connectivity = "CONNECTIVITY_STATE", - audioType = "AUDIO_TYPE", - applicationState = "APP_STATE", - created = "CREATED_DATA", - feedbackId = "FEEDBACK_ID", - userId = "USER_ID" - ) - - wrapper = FeedbackMetadataWrapper( - sessionIdentifier = SESSION_IDENTIFIER, - driverModeStartTime = DRIVER_MODE_START_TIME, - driverModeIdentifier = DRIVER_MODE_IDENTIFIER, - driverMode = DRIVER_MODE, - rerouteCount = REROUTE_COUNT, - locationEngineNameExternal = "LOCATION_ENGINE_NAME_EXTERNAL", - percentTimeInPortrait = 50, - percentTimeInForeground = 20, - eventVersion = 100, - lastLocation = Point.fromLngLat(30.0, 40.0), - phoneState = phoneState, - metricsDirectionsRoute = MetricsDirectionsRoute(route = null), - metricsRouteProgress = MetricsRouteProgress(routeProgress = null), - appMetadata = AppMetadata( - name = "APP_METADATA_NAME", - version = "APP_METADATA_VERSION", - userId = "APP_METADATA_USER_ID", - sessionId = "APP_METADATA_SESSION_ID", - ), - locationsCollector = locationsCollector, - ) - } - - @Test - fun `metadata without locations`() { - val metadata = wrapper.get() - - checkMetadataFields(metadata) - verify(exactly = 1) { - locationsCollector.flushBufferFor(locationsCollectorListenerSlot.captured) - } - } - @Test - fun `metadata with locations when buffer full`() { - locationsCollectorListenerSlot.captured.onBufferFull(locationsBefore, locationsAfter) - - val metadata = wrapper.get() - - checkMetadataFields( - metadata, - locationsBefore.toTelemetryLocations(), - locationsAfter.toTelemetryLocations() - ) - verify(exactly = 0) { - locationsCollector.flushBufferFor(any()) + fun getMethodTest() { + val mockMetadata = mockk() + val mockUserFeedbackHandle = mockk { + every { metadata } returns mockMetadata } - } + val feedbackMetadataWrapper = provideFeedbackMetadataWrapper(mockUserFeedbackHandle) - @Test - fun `metadata with locations forced to assemble`() { - every { - locationsCollector.flushBufferFor(locationsCollectorListenerSlot.captured) - } answers { - locationsCollectorListenerSlot.captured.onBufferFull(locationsBefore, locationsAfter) - } - - val metadata = wrapper.get() + val feedbackMetadata = feedbackMetadataWrapper.get() - checkMetadataFields( - metadata, - locationsBefore.toTelemetryLocations(), - locationsAfter.toTelemetryLocations() - ) - verify(exactly = 1) { - locationsCollector.flushBufferFor(locationsCollectorListenerSlot.captured) - } + assertNotNull(feedbackMetadata) + verify (exactly = 1) { mockUserFeedbackHandle.metadata } } - private fun checkMetadataFields( - feedbackMetadata: FeedbackMetadata, - locationsBefore: Array = arrayOf(), - locationsAfter: Array = arrayOf(), - ) { - assertEquals(SESSION_IDENTIFIER, feedbackMetadata.sessionIdentifier) - assertEquals(DRIVER_MODE_IDENTIFIER, feedbackMetadata.driverModeIdentifier) - assertEquals(DRIVER_MODE, feedbackMetadata.driverMode) - assertEquals(DRIVER_MODE_START_TIME, feedbackMetadata.driverModeStartTime) - assertEquals(REROUTE_COUNT, feedbackMetadata.rerouteCount) - assertEquals(phoneState, feedbackMetadata.phoneState) - assertArrayEquals(locationsBefore, feedbackMetadata.locationsBeforeEvent) - assertArrayEquals(locationsAfter, feedbackMetadata.locationsAfterEvent) - } + private fun provideFeedbackMetadataWrapper( + mockUserFeedbackHandle: UserFeedbackHandle = mockk() + ): FeedbackMetadataWrapper = + FeedbackMetadataWrapper(mockUserFeedbackHandle) } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationArriveEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationArriveEventTest.kt deleted file mode 100644 index 6007cd74fe0..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationArriveEventTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.fillValues -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyNavigationEventFields -import org.junit.Test - -class NavigationArriveEventTest { - - @Test - fun testValue() { - val arriveEvent = NavigationArriveEvent(EventsTestHelper.mockPhoneState()).apply { - fillValues() - } - - val toValue = arriveEvent.toValue() - - toValue.verifyNavigationEventFields(arriveEvent.event) - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationCancelEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationCancelEventTest.kt deleted file mode 100644 index 337ad70d371..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationCancelEventTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.fillValues -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyNavigationEventFields -import org.junit.Assert.assertEquals -import org.junit.Test - -class NavigationCancelEventTest { - - @Test - fun testValue() { - val cancelEvent = NavigationCancelEvent(EventsTestHelper.mockPhoneState()).apply { - fillValues() - arrivalTimestamp = "arrivalTimestamp_0" - rating = 999 - comment = "comment_0" - } - - val toValue = cancelEvent.toValue() - - toValue.verifyNavigationEventFields(cancelEvent.event) - (toValue.contents as Map).let { content -> - assertEquals(cancelEvent.arrivalTimestamp, content["arrivalTimestamp"]!!.contents) - assertEquals(cancelEvent.rating.toLong(), content["rating"]!!.contents) - assertEquals(cancelEvent.comment, content["comment"]!!.contents) - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationCustomEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationCustomEventTest.kt deleted file mode 100644 index 46d834223c0..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationCustomEventTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import org.junit.Assert.assertEquals -import org.junit.Test - -class NavigationCustomEventTest { - - @Test - fun testValue() { - val cancelEvent = NavigationCustomEvent().apply { - type = "type_0" - payload = "payload_0" - customEventVersion = "customEventVersion_0" - - createdMonotime = 123 - driverMode = "driverMode_0" - driverModeStartTimestampMonotime = 124 - sdkIdentifier = "sdkIdentifier_0" - eventVersion = 125 - simulation = true - locationEngine = "locationEngine" - lat = 10.1 - lng = 11.2 - } - - val toValue = cancelEvent.toValue() - - (toValue.contents as Map).let { content -> - // val - assertEquals(cancelEvent.version, content["version"]!!.contents) - assertEquals(cancelEvent.driverModeId, content["driverModeId"]!!.contents) - assertEquals(cancelEvent.event, content["event"]!!.contents) - assertEquals(cancelEvent.created, content["created"]!!.contents) - assertEquals(cancelEvent.operatingSystem, content["operatingSystem"]!!.contents) - assertEquals( - cancelEvent.driverModeStartTimestamp, - content["driverModeStartTimestamp"]!!.contents - ) - - // var - assertEquals(cancelEvent.type, content["type"]!!.contents) - assertEquals(cancelEvent.payload, content["payload"]!!.contents) - assertEquals(cancelEvent.customEventVersion, content["customEventVersion"]!!.contents) - assertEquals( - cancelEvent.createdMonotime.toLong(), - content["createdMonotime"]!!.contents - ) - assertEquals(cancelEvent.driverMode, content["driverMode"]!!.contents) - assertEquals( - cancelEvent.driverModeStartTimestampMonotime.toLong(), - content["driverModeStartTimestampMonotime"]!!.contents - ) - assertEquals(cancelEvent.sdkIdentifier, content["sdkIdentifier"]!!.contents) - assertEquals(cancelEvent.eventVersion.toLong(), content["eventVersion"]!!.contents) - assertEquals(cancelEvent.simulation, content["simulation"]!!.contents) - assertEquals(cancelEvent.locationEngine, content["locationEngine"]!!.contents) - assertEquals(cancelEvent.lat, content["lat"]!!.contents) - assertEquals(cancelEvent.lng, content["lng"]!!.contents) - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationDepartEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationDepartEventTest.kt deleted file mode 100644 index 0f2ab3676b4..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationDepartEventTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.fillValues -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyNavigationEventFields -import org.junit.Test - -class NavigationDepartEventTest { - - @Test - fun testValue() { - val departEvent = NavigationDepartEvent(EventsTestHelper.mockPhoneState()).apply { - fillValues() - } - - val toValue = departEvent.toValue() - - toValue.verifyNavigationEventFields(departEvent.event) - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationFeedbackEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationFeedbackEventTest.kt deleted file mode 100644 index ec63d6aa16a..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationFeedbackEventTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.fillValues -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyNavigationEventFields -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyTelemetryLocations -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -class NavigationFeedbackEventTest { - - @Test - fun testValue() { - val feedbackEvent = NavigationFeedbackEvent( - EventsTestHelper.mockPhoneState(), - NavigationStepData(MetricsRouteProgress(null)) - ).apply { - fillValues() - feedbackType = "feedbackType_0" - source = "source_0" - description = "description_0" - locationsBefore = arrayOf( - TelemetryLocation( - 2.2, - 3.3, - 10.0f, - 11.0f, - 12.0, - "timestamp_0", - 13.0f, - 15.0f - ) - ) - locationsAfter = arrayOf( - TelemetryLocation( - 12.2, - 13.3, - 110.0f, - 111.0f, - 112.0, - "timestamp_1", - 113.0f, - 115.0f - ) - ) - screenshot = "screenshot_0" - feedbackSubType = arrayOf("feedbackSubType_0", "feedbackSubType_1") - } - - val toValue = feedbackEvent.toValue() - - toValue.verifyNavigationEventFields(feedbackEvent.event) - (toValue.contents as Map).let { content -> - assertEquals(feedbackEvent.userId, content["userId"]!!.contents) - assertEquals(feedbackEvent.feedbackId, content["feedbackId"]!!.contents) - assertEquals(feedbackEvent.feedbackType, content["feedbackType"]!!.contents) - assertEquals(feedbackEvent.source, content["source"]!!.contents) - assertEquals(feedbackEvent.description, content["description"]!!.contents) - assertEquals(feedbackEvent.screenshot, content["screenshot"]!!.contents) - assertTrue(content.containsKey("locationsBefore")) - (content["locationsBefore"] as Value) - .verifyTelemetryLocations(feedbackEvent.locationsBefore!!) - assertTrue(content.containsKey("locationsAfter")) - (content["locationsAfter"] as Value) - .verifyTelemetryLocations(feedbackEvent.locationsAfter!!) - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationFreeDriveEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationFreeDriveEventTest.kt deleted file mode 100644 index ab2c86d40cd..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationFreeDriveEventTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyAppMetadata -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyTelemetryLocation -import org.junit.Assert.assertEquals -import org.junit.Test - -class NavigationFreeDriveEventTest { - - @Test - fun testValue() { - val freeDriveEvent = NavigationFreeDriveEvent(EventsTestHelper.mockPhoneState()).apply { - eventVersion = 100 - locationEngine = "locationEngine_0" - percentTimeInPortrait = 101 - percentTimeInForeground = 102 - simulation = true - navigatorSessionIdentifier = "navigatorSessionIdentifier_0" - startTimestamp = "startTimestamp_0" - sessionIdentifier = "sessionIdentifier_0" - location = TelemetryLocation( - 2.2, - 3.3, - 10.0f, - 11.0f, - 12.0, - "timestamp_0", - 13.0f, - 15.0f - ) - eventType = "eventType_0" - appMetadata = AppMetadata( - name = "APP_METADATA_NAME", - version = "APP_METADATA_VERSION", - userId = "APP_METADATA_USER_ID", - sessionId = "APP_METADATA_SESSION_ID", - ) - } - - val toValue = freeDriveEvent.toValue() - - (toValue.contents as Map).let { content -> - assertEquals(freeDriveEvent.version, content["version"]!!.contents) - assertEquals(freeDriveEvent.created, content["created"]!!.contents) - assertEquals(freeDriveEvent.volumeLevel.toLong(), content["volumeLevel"]!!.contents) - assertEquals(freeDriveEvent.batteryLevel.toLong(), content["batteryLevel"]!!.contents) - assertEquals( - freeDriveEvent.screenBrightness.toLong(), - content["screenBrightness"]!!.contents - ) - assertEquals(freeDriveEvent.batteryPluggedIn, content["batteryPluggedIn"]!!.contents) - assertEquals(freeDriveEvent.connectivity, content["connectivity"]!!.contents) - assertEquals(freeDriveEvent.audioType, content["audioType"]!!.contents) - assertEquals(freeDriveEvent.applicationState, content["applicationState"]!!.contents) - assertEquals(freeDriveEvent.event, content["event"]!!.contents) - assertEquals(freeDriveEvent.eventVersion.toLong(), content["eventVersion"]!!.contents) - assertEquals(freeDriveEvent.locationEngine, content["locationEngine"]!!.contents) - assertEquals( - freeDriveEvent.percentTimeInPortrait.toLong(), - content["percentTimeInPortrait"]!!.contents - ) - assertEquals( - freeDriveEvent.percentTimeInForeground.toLong(), - content["percentTimeInForeground"]!!.contents - ) - assertEquals(freeDriveEvent.simulation, content["simulation"]!!.contents) - assertEquals( - freeDriveEvent.navigatorSessionIdentifier, - content["navigatorSessionIdentifier"]!!.contents - ) - assertEquals(freeDriveEvent.startTimestamp, content["startTimestamp"]!!.contents) - assertEquals(freeDriveEvent.sessionIdentifier, content["sessionIdentifier"]!!.contents) - assertEquals(freeDriveEvent.eventType, content["eventType"]!!.contents) - content["location"]!!.verifyTelemetryLocation(freeDriveEvent.location!!) - content["appMetadata"]!!.verifyAppMetadata(freeDriveEvent.appMetadata!!) - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationRerouteEventTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationRerouteEventTest.kt deleted file mode 100644 index d7288010bd8..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/NavigationRerouteEventTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.mapbox.bindgen.Value -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.fillValues -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyNavigationEventFields -import com.mapbox.navigation.core.telemetry.events.EventsTestHelper.verifyTelemetryLocations -import com.mapbox.navigation.core.testutil.EventsProvider -import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertTrue -import org.junit.Assert -import org.junit.Test - -class NavigationRerouteEventTest { - - @Test - fun testValue() { - val rerouteEvent = NavigationRerouteEvent( - EventsTestHelper.mockPhoneState(), - EventsProvider.mockNavigationStepData() - ).apply { - fillValues() - newDistanceRemaining = 50 - newDurationRemaining = 51 - newGeometry = "newGeometry_0" - secondsSinceLastReroute = 52 - locationsBefore = arrayOf( - TelemetryLocation( - 2.2, - 3.3, - 10.0f, - 11.0f, - 12.0, - "timestamp_0", - 13.0f, - 15.0f, - ) - ) - locationsAfter = arrayOf( - TelemetryLocation( - 12.2, - 13.3, - 110.0f, - 111.0f, - 112.0, - "timestamp_1", - 113.0f, - 115.0f, - ) - ) - screenshot = "screenshot_0" - } - - val toValue = rerouteEvent.toValue() - - toValue.verifyNavigationEventFields(rerouteEvent.event) - (toValue.contents as Map).let { content -> - assertEquals(rerouteEvent.feedbackId, content["feedbackId"]!!.contents) - assertEquals( - rerouteEvent.newDistanceRemaining.toLong(), - content["newDistanceRemaining"]!!.contents - ) - assertEquals( - rerouteEvent.newDurationRemaining.toLong(), - content["newDurationRemaining"]!!.contents - ) - assertEquals(rerouteEvent.newGeometry, content["newGeometry"]!!.contents) - assertEquals( - rerouteEvent.secondsSinceLastReroute.toLong(), - content["secondsSinceLastReroute"]!!.contents - ) - assertEquals(rerouteEvent.screenshot, content["screenshot"]!!.contents) - assertTrue(content.containsKey("locationsBefore")) - (content["locationsBefore"] as Value) - .verifyTelemetryLocations(rerouteEvent.locationsBefore!!) - assertTrue(content.containsKey("locationsAfter")) - (content["locationsAfter"] as Value) - .verifyTelemetryLocations(rerouteEvent.locationsAfter!!) - // NavigationStepData verify - assertTrue(content.containsKey("step")) - (content["step"]!!.contents as HashMap).let { stepContent -> - Assert.assertEquals(1L, stepContent["durationRemaining"]!!.contents) - Assert.assertEquals(2L, stepContent["distance"]!!.contents) - Assert.assertEquals(3L, stepContent["distanceRemaining"]!!.contents) - Assert.assertEquals(4L, stepContent["duration"]!!.contents) - Assert.assertEquals("upcomingName_0", stepContent["upcomingName"]!!.contents) - Assert.assertEquals( - "upcomingModifier_0", - stepContent["upcomingModifier"]!!.contents - ) - Assert.assertEquals( - "previousInstruction_0", - stepContent["previousInstruction"]!!.contents - ) - Assert.assertEquals("previousName_0", stepContent["previousName"]!!.contents) - Assert.assertEquals( - "upcomingInstruction_0", - stepContent["upcomingInstruction"]!!.contents - ) - Assert.assertEquals("previousType_0", stepContent["previousType"]!!.contents) - Assert.assertEquals("upcomingType_0", stepContent["upcomingType"]!!.contents) - Assert.assertEquals( - "previousModifier_0", - stepContent["previousModifier"]!!.contents - ) - } - } - } -} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/SchemaTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/SchemaTest.kt deleted file mode 100644 index 21b2f5890f6..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/telemetry/events/SchemaTest.kt +++ /dev/null @@ -1,395 +0,0 @@ -package com.mapbox.navigation.core.telemetry.events - -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import com.google.gson.JsonPrimitive -import com.google.gson.annotations.SerializedName -import com.mapbox.navigation.base.metrics.NavigationMetrics -import org.apache.commons.io.IOUtils -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Test -import java.io.BufferedReader -import java.io.ByteArrayInputStream -import java.io.IOException -import java.io.InputStreamReader -import java.lang.reflect.Field -import java.lang.reflect.Modifier -import java.util.ArrayList -import java.util.zip.GZIPInputStream - -/** - * Note: [NavigationStepData.distanceRemaining] -> stepdistanceRemaining and - * [NavigationStepData.durationRemaining] -> stepdurationRemaining to avoid names collision - */ -class SchemaTest { - - private val eventSchemas: List = unpackSchemas() - - @Test - @Throws(Exception::class) - fun checkNavigationArriveEventSize() { - val schema = grabEventSchema(NavigationMetrics.ARRIVE) - val fields = grabSchemaPropertyFields(NavigationArriveEvent::class.java) - assertEquals(schema.properties.size().toLong(), fields.size.toLong()) - } - - @Test - @Throws(Exception::class) - fun checkNavigationArriveEventFields() { - val schema = grabEventSchema(NavigationMetrics.ARRIVE) - val fields = grabSchemaPropertyFields(NavigationArriveEvent::class.java) - schemaContainsPropertyFields(schema.properties, fields) - propertiesFieldsContainsSchemaFields(fields, schema.properties) - } - - @Test - @Throws(Exception::class) - fun checkNavigationCancelEventSize() { - val schema = grabEventSchema(NavigationMetrics.CANCEL_SESSION) - val fields = grabSchemaPropertyFields(NavigationCancelEvent::class.java) - assertEquals(schema.properties.size().toLong(), fields.size.toLong()) - } - - @Test - @Throws(Exception::class) - fun checkNavigationCancelEventFields() { - val schema = grabEventSchema(NavigationMetrics.CANCEL_SESSION) - val fields = grabSchemaPropertyFields(NavigationCancelEvent::class.java) - schemaContainsPropertyFields(schema.properties, fields) - propertiesFieldsContainsSchemaFields(fields, schema.properties) - } - - @Test - @Throws(Exception::class) - fun checkNavigationDepartEventSize() { - val schema = grabEventSchema(NavigationMetrics.DEPART) - val fields = grabSchemaPropertyFields(NavigationDepartEvent::class.java) - assertEquals(schema.properties.size().toLong(), fields.size.toLong()) - } - - @Test - @Throws(Exception::class) - fun checkNavigationDepartEventFields() { - val schema = grabEventSchema(NavigationMetrics.DEPART) - val fields = grabSchemaPropertyFields(NavigationDepartEvent::class.java) - schemaContainsPropertyFields(schema.properties, fields) - propertiesFieldsContainsSchemaFields(fields, schema.properties) - } - - @Test - @Throws(Exception::class) - fun checkNavigationFeedbackEventSize() { - val schema = grabEventSchema(NavigationMetrics.FEEDBACK, version = "2.2") - val fields = grabSchemaPropertyFields(NavigationFeedbackEvent::class.java) - assertEquals(schema.properties.size().toLong(), fields.size.toLong()) - } - - @Test - @Throws(Exception::class) - fun checkNavigationFeedbackEventFields() { - val schema = grabEventSchema(NavigationMetrics.FEEDBACK, version = "2.2") - val fields = grabSchemaPropertyFields(NavigationFeedbackEvent::class.java) - schemaContainsPropertyFields(schema.properties, fields) - propertiesFieldsContainsSchemaFields(fields, schema.properties) - } - - @Test - @Throws(Exception::class) - fun checkNavigationRerouteEventSize() { - val schema = grabEventSchema(NavigationMetrics.REROUTE) - val fields = grabSchemaPropertyFields(NavigationRerouteEvent::class.java) - assertEquals(schema.properties.size().toLong(), fields.size.toLong()) - } - - @Test - fun checkNavigationRerouteEventFields() { - val schema = grabEventSchema(NavigationMetrics.REROUTE) - val fields = grabSchemaPropertyFields(NavigationRerouteEvent::class.java) - schemaContainsPropertyFields(schema.properties, fields) - propertiesFieldsContainsSchemaFields(fields, schema.properties) - } - - @Test - fun checkNavigationFreeDriveEventSize() { - val schema = grabEventSchema(NavigationMetrics.FREE_DRIVE) - val fields = grabSchemaPropertyFields(NavigationFreeDriveEvent::class.java) - assertEquals(schema.properties.size().toLong(), fields.size.toLong()) - } - - @Test - fun checkNavigationFreeDriveEventFields() { - val schema = grabEventSchema(NavigationMetrics.FREE_DRIVE) - val fields = grabSchemaPropertyFields(NavigationFreeDriveEvent::class.java) - schemaContainsPropertyFields(schema.properties, fields) - propertiesFieldsContainsSchemaFields(fields, schema.properties) - } - - private fun schemaContainsPropertyFields( - properties: JsonObject, - fields: List - ) { - var distanceRemainingCount = 0 - var durationRemainingCount = 0 - for (i in fields.indices) { - val thisField = fields[i].toString() - val fieldArray = thisField.split(" ".toRegex()).toTypedArray() - val typeArray = fieldArray[fieldArray.size - 2].split("\\.".toRegex()).toTypedArray() - val type = typeArray[typeArray.size - 1] - val nameArray = fieldArray[fieldArray.size - 1].split("\\.".toRegex()).toTypedArray() - var field = nameArray[nameArray.size - 1] - val serializedName = fields[i].getAnnotation(SerializedName::class.java) - if (serializedName != null) { - field = serializedName.value - } - if (field.equals("durationRemaining", ignoreCase = true)) { - durationRemainingCount++ - if (durationRemainingCount > 1) { - field = "step$field" - } - } - if (field.equals("distanceRemaining", ignoreCase = true)) { - distanceRemainingCount++ - if (distanceRemainingCount > 1) { - field = "step$field" - } - } - val thisSchema = findSchema(properties, field) - assertNotNull(field, thisSchema) - if (thisSchema.has("type")) { - typesMatch(thisSchema, type) - } - verifyProperty(thisSchema, fields[i].type, fields[i].name) - } - } - - private fun propertiesFieldsContainsSchemaFields(fields: List, properties: JsonObject) { - val missingFields = mutableListOf() - var stepdistanceRemainingCatch = false - var stepdurationRemainingCatch = false - properties.keySet().forEach { jsonProperty -> - val exist = fields.any { it.name == jsonProperty } - if (!exist) { - if ( - jsonProperty == "stepdurationRemaining" && !stepdurationRemainingCatch - ) { - stepdurationRemainingCatch = true - } else if ( - jsonProperty == "stepdistanceRemaining" && !stepdistanceRemainingCatch - ) { - stepdistanceRemainingCatch = true - } else { - missingFields.add(jsonProperty) - } - } - } - assertTrue("Missing fields: $missingFields", missingFields.isEmpty()) - } - - private fun verifyProperty(property: JsonObject, propertyImpl: Class<*>, fieldName: String) { - val propertyType = property.get("type")?.let { element -> - return@let if (element.isJsonArray) { - // filter out nullable options, and don't expect multiple types - (element as JsonArray).first { it.asString != "null" }.asString - } else { - element.asString - } - } ?: return - - val expectedPropertyType: String = when { - Number::class.java.isAssignableFrom(propertyImpl) || - propertyImpl.simpleName.equals("short", ignoreCase = true) || - propertyImpl.simpleName.equals("int", ignoreCase = true) || - propertyImpl.simpleName.equals("long", ignoreCase = true) || - propertyImpl.simpleName.equals("float", ignoreCase = true) || - propertyImpl.simpleName.equals("double", ignoreCase = true) -> "number" - Boolean::class.java.isAssignableFrom(propertyImpl) || - propertyImpl.simpleName.equals("boolean", ignoreCase = true) -> "boolean" - String::class.java.isAssignableFrom(propertyImpl) -> "string" - propertyImpl.isArray || List::class.java.isAssignableFrom(propertyImpl) -> "array" - else -> "object" - } - assertEquals("Incorrect type for $fieldName", propertyType, expectedPropertyType) - - if (propertyType == "object") { - val objectProperties = property.getAsJsonObject("properties") - // filtering out synthetic fields injected by jacoco, - // see https://github.com/jacoco/jacoco/issues/168 - val propertyFields = propertyImpl.declaredFields.filter { it.isSynthetic.not() } - assertEquals( - "schema and impl fields count should match for $fieldName", - objectProperties.keySet().size, - propertyFields.size - ) - - propertyFields.forEach { objectField -> - val name = objectField - .getAnnotation(SerializedName::class.java) - ?.value ?: objectField.name - assertTrue( - "schema and impl object $fieldName should both have a $name property", - objectProperties.has(name) - ) - val objectProperty = objectProperties.get(name).asJsonObject - verifyProperty(objectProperty, objectField.type, objectField.name) - } - } else if (propertyType == "array") { - val arrayItem = property.getAsJsonObject("items") - verifyProperty(arrayItem, propertyImpl.getGenericListImplClass(), fieldName) - } - } - - private fun Class<*>.getGenericListImplClass(): Class<*> { - return if (this.typeName.endsWith("[]")) { - Class.forName(this.typeName.substring(0, this.typeName.length - 2)) - } else { - throw IllegalArgumentException("${this.typeName} is not a list type") - } - } - - private fun findSchema(schema: JsonObject, field: String): JsonObject { - return schema.getAsJsonObject(field) - } - - private fun typesMatch(schema: JsonObject, type: String) { - val eventType = when { - type.equals("int", ignoreCase = true) || - type.equals("integer", ignoreCase = true) || - type.equals("double", ignoreCase = true) || - type.equals("float", ignoreCase = true) -> "number" - type.contains("[]") -> "array" - type.equals("string", ignoreCase = true) -> "string" - type.equals("boolean", ignoreCase = true) -> "boolean" - else -> "object" - } - val typeClass: Class = schema["type"].javaClass - val jsonElement = JsonParser().parse(eventType.toLowerCase()) - if (typeClass == JsonPrimitive::class.java) { - val typePrimitive = schema["type"] - assertEquals(typePrimitive, jsonElement) - } else { - val arrayOfTypes = schema.getAsJsonArray("type") - assertTrue(arrayOfTypes.contains(jsonElement)) - } - } - - private fun grabSchemaPropertyFields(aClass: Class<*>): List { - val fields: MutableList = ArrayList() - val allFields = aClass.declaredFields - for (field in allFields) { - if (field.type == NavigationStepData::class.java) { - val dataFields = - field.type.declaredFields - for (dataField in dataFields) { - if (Modifier.isPrivate(dataField.modifiers) && - !Modifier.isStatic(dataField.modifiers) - ) { - fields.add(dataField) - } - } - } else if ( - Modifier.isPrivate(field.modifiers) && !Modifier.isStatic(field.modifiers) - ) { - fields.add(field) - } - } - val superFields = aClass.superclass!!.declaredFields - for (field in superFields) { - if (Modifier.isPrivate(field.modifiers) && !Modifier.isStatic(field.modifiers)) { - fields.add(field) - } - } - - // filter out non-property fields - fields.removeIf { it.name == "version" } - return fields - } - - private fun grabEventSchema(eventName: String, version: String = "2.2"): EventSchema = - eventSchemas.filter { it.name == eventName && it.version == version }.let { - when { - it.isEmpty() -> { - throw IllegalArgumentException( - "missing $eventName schema for version $version" - ) - } - it.size > 1 -> { - throw IllegalArgumentException( - "multiple $eventName schemas for version $version" - ) - } - else -> { - it.first() - } - } - } - - @Throws(IOException::class) - private fun unpackSchemas(): List { - val inputStream = - SchemaTest::class.java.classLoader!! - .getResourceAsStream("mobile-event-schemas.jsonl.gz") - val byteOut = IOUtils.toByteArray(inputStream) - val schemaFileStream = ByteArrayInputStream(byteOut) - val gzis = GZIPInputStream(schemaFileStream) - val reader = InputStreamReader(gzis) - val `in` = BufferedReader(reader) - val schemaList = mutableListOf() - val gson = Gson() - var read: String? - while (`in`.readLine().also { read = it } != null) { - val schema = gson.fromJson(read, JsonObject::class.java) - val name = schema["name"].asString - val version = schema["version"].asString - val properties = getProperties(schema) - schemaList.add(EventSchema(name, version, properties)) - } - return schemaList - } - - @Throws(IOException::class) - private fun getProperties(schema: JsonObject): JsonObject { - val gson = Gson() - var schemaJson = gson.toJson(schema["properties"]) - var properties = gson.fromJson(schema["properties"], JsonObject::class.java) - if (properties.has("step")) { - val stepJson = properties["step"].asJsonObject - val stepProperties = stepJson["properties"].asJsonObject - val stepPropertiesJson = gson.toJson(stepProperties) - schemaJson = generateStepSchemaJson(stepPropertiesJson, schemaJson) - properties = gson.fromJson(schemaJson, JsonObject::class.java) - properties.remove("step") - } - properties.remove("userAgent") - properties.remove("received") - properties.remove("token") - properties.remove("authorization") - properties.remove("owner") - properties.remove("locationAuthorization") - properties.remove("locationEnabled") - // temporary need to work out a solution to include this data - properties.remove("platform") - properties.remove("version") - return properties - } - - private fun generateStepSchemaJson( - stepJson: String, - schemaString: String - ): String { - var stepJson = stepJson - var schemaString = schemaString - stepJson = stepJson.replace("\"distanceRemaining\"", "\"stepdistanceRemaining\"") - stepJson = stepJson.replace("durationRemaining", "stepdurationRemaining") - stepJson = stepJson.replaceFirst("\\{".toRegex(), ",") - schemaString = schemaString.replace("}$".toRegex(), "") - schemaString += stepJson - return schemaString - } -} - -private data class EventSchema(val name: String, val version: String, val properties: JsonObject) diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/testutil/EventsProvider.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/testutil/EventsProvider.kt deleted file mode 100644 index bed76f44f95..00000000000 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/testutil/EventsProvider.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.mapbox.navigation.core.testutil - -import com.mapbox.navigation.core.telemetry.events.NavigationStepData -import com.mapbox.navigation.core.telemetry.events.TelemetryLocation -import io.mockk.every -import io.mockk.mockk - -internal object EventsProvider { - - fun provideDefaultTelemetryLocationsArray( - telemetryLocations: Array = arrayOf( - TelemetryLocation( - latitude = 1.1, - longitude = 2.2, - speed = 3.3f, - bearing = 4.4f, - altitude = 5.5, - timestamp = "timestamp_0", - horizontalAccuracy = 6.6f, - verticalAccuracy = 7.7f, - ), - TelemetryLocation( - latitude = 1.2, - longitude = 2.3, - speed = 3.4f, - bearing = 4.5f, - altitude = 5.6, - timestamp = "timestamp_1", - horizontalAccuracy = 6.7f, - verticalAccuracy = 7.8f, - ) - ) - ): Array = telemetryLocations - - fun mockNavigationStepData(): NavigationStepData = mockk { - every { durationRemaining } returns 1 - every { distance } returns 2 - every { distanceRemaining } returns 3 - every { duration } returns 4 - every { upcomingName } returns "upcomingName_0" - every { upcomingModifier } returns "upcomingModifier_0" - every { previousInstruction } returns "previousInstruction_0" - every { previousName } returns "previousName_0" - every { upcomingInstruction } returns "upcomingInstruction_0" - every { previousType } returns "previousType_0" - every { upcomingType } returns "upcomingType_0" - every { previousModifier } returns "previousModifier_0" - } -} diff --git a/libnavigation-core/src/test/resources/feedback_metadata.json b/libnavigation-core/src/test/resources/feedback_metadata.json deleted file mode 100644 index bfaadcef937..00000000000 --- a/libnavigation-core/src/test/resources/feedback_metadata.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "driverMode": "trip", - "driverModeIdentifier": "5744c612-010a-46b9-a703-b33e8102dd65", - "driverModeStartTime": "2021-09-07T16:49:40.984+0300", - "eventVersion": 7, - "lastLocation": { - "coordinates": [ - 27.612405649097575, - 53.92678771372226 - ], - "type": "Point" - }, - "locationEngineNameExternal": "com.mapbox.android.core.location.LocationEngineProxy", - "locationsAfterEvent": [ - { - "altitude": 0.0, - "course": 155.79807, - "horizontalAccuracy": 3.9, - "lat": 53.92668289404487, - "lng": 27.61248547382584, - "speed": 13.888889, - "timestamp": "1631022586464", - "verticalAccuracy": 0.0 - }, - { - "altitude": 249.23427606395424, - "course": 321.4587, - "horizontalAccuracy": 18.791, - "lat": 53.927929, - "lng": 27.61081, - "speed": 0.06514638, - "timestamp": "1631022587025", - "verticalAccuracy": 0.0 - }, - { - "altitude": 0.0, - "course": 155.79796, - "horizontalAccuracy": 3.9, - "lat": 53.92646551557083, - "lng": 27.612651016957173, - "speed": 13.888889, - "timestamp": "1631022588463", - "verticalAccuracy": 0.0 - } - ], - "locationsBeforeEvent": [ - { - "altitude": 0.0, - "course": 155.7984, - "horizontalAccuracy": 3.530373, - "lat": 53.927342774045385, - "lng": 27.611982946599667, - "speed": 13.888889, - "timestamp": "1631022580462", - "verticalAccuracy": 0.0 - }, - { - "altitude": 0.0, - "course": 155.79836, - "horizontalAccuracy": 3.9, - "lat": 53.927232034455315, - "lng": 27.61206727959806, - "speed": 13.888889, - "timestamp": "1631022581461", - "verticalAccuracy": 0.0 - }, - { - "altitude": 249.27823636720268, - "course": 310.59564, - "horizontalAccuracy": 18.512, - "lat": 53.9279263, - "lng": 27.6108134, - "speed": 0.03092535, - "timestamp": "1631022582014", - "verticalAccuracy": 0.0 - }, - { - "altitude": 0.0, - "course": 155.79823, - "horizontalAccuracy": 3.9, - "lat": 53.92700816626692, - "lng": 27.612237764928203, - "speed": 13.888889, - "timestamp": "1631022583462", - "verticalAccuracy": 0.0 - }, - { - "altitude": 0.0, - "course": 155.79817, - "horizontalAccuracy": 3.9, - "lat": 53.92689628958087, - "lng": 27.612322963874483, - "speed": 13.888889, - "timestamp": "1631022584463", - "verticalAccuracy": 0.0 - }, - { - "altitude": 0.0, - "course": 155.79811, - "horizontalAccuracy": 3.9, - "lat": 53.92678771372226, - "lng": 27.612405649097575, - "speed": 13.888889, - "timestamp": "1631022585462", - "verticalAccuracy": 0.0 - } - ], - "metricsDirectionsRoute": { - "distance": 744, - "duration": 52, - "stepCount": 65 - }, - "metricsRouteProgress": { - "currentStepDistance": 354, - "currentStepDistanceRemaining": 285, - "currentStepDuration": 52, - "currentStepDurationRemaining": 42, - "directionsRouteDistance": 821, - "directionsRouteDuration": 27, - "directionsRouteIndex": 5, - "directionsRouteStepCount": 20, - "distanceRemaining": 141, - "distanceTraveled": 338, - "durationRemaining": 35, - "legCount": 7, - "legIndex": 2, - "previousStepInstruction": "Drive south on улица Толбухина.", - "previousStepName": "улица Толбухина", - "previousStepType": "depart", - "stepCount": 83, - "stepIndex": 56, - "upcomingStepInstruction": "Turn left onto проспект Независимости.", - "upcomingStepModifier": "left", - "upcomingStepName": "проспект Независимости", - "upcomingStepType": "end of road" - }, - "percentTimeInForeground": 100, - "percentTimeInPortrait": 100, - "phoneState": { - "applicationState": "Foreground", - "audioType": "headphones", - "batteryLevel": 41, - "connectivity": "Unknown", - "created": "2021-09-07T16:49:46.061+0300", - "feedbackId": "31b941dd-6da2-4fb0-b601-2602b7075fc6", - "isBatteryPluggedIn": true, - "screenBrightness": 61, - "userId": "ef8a4675-a350-49b1-9c26-d2ddcaeefc1a", - "volumeLevel": 0 - }, - "rerouteCount": 1, - "sessionIdentifier": "317a1e3b-7c7b-46cd-9eea-212d00e9b29b" -} \ No newline at end of file diff --git a/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/MapboxMetricsReporter.kt b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/MapboxMetricsReporter.kt index 0dcba493bdc..ed61fcef342 100644 --- a/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/MapboxMetricsReporter.kt +++ b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/MapboxMetricsReporter.kt @@ -1,6 +1,7 @@ package com.mapbox.navigation.metrics import android.content.Context +import android.os.Handler import com.google.gson.Gson import com.mapbox.bindgen.Value import com.mapbox.common.Event @@ -16,6 +17,8 @@ import com.mapbox.navigation.base.internal.metric.extractEventsNames import com.mapbox.navigation.base.metrics.MetricEvent import com.mapbox.navigation.base.metrics.MetricsObserver import com.mapbox.navigation.base.metrics.MetricsReporter +import com.mapbox.navigation.metrics.events.EventsServiceInterfacesManager +import com.mapbox.navigation.metrics.events.TelemetryEventsProvider import com.mapbox.navigation.metrics.internal.EventsServiceProvider import com.mapbox.navigation.metrics.internal.TelemetryServiceProvider import com.mapbox.navigation.metrics.internal.TelemetryUtilsDelegate @@ -33,7 +36,7 @@ object MapboxMetricsReporter : MetricsReporter { private const val LOG_CATEGORY = "MapboxMetricsReporter" private val gson = Gson() - private lateinit var eventsService: EventsServiceInterface + private lateinit var eventsManager: EventsServiceInterfacesManager private lateinit var telemetryService: TelemetryService @Volatile @@ -62,6 +65,12 @@ object MapboxMetricsReporter : MetricsReporter { } } + /** + * Events priority. See [EventPriority] + */ + @Volatile + var eventsPriority: EventPriority = EventPriority.IMMEDIATE + /** * Initialize [EventsServiceInterface] and [TelemetryService] that need to send event to * Mapbox Telemetry server. @@ -77,10 +86,10 @@ object MapboxMetricsReporter : MetricsReporter { userAgent: String ) { isTelemetryInitialized = true + eventsManager = TelemetryEventsProvider.getOrCreateEventsServiceInterfacesManager(accessToken, userAgent) val eventsServerOptions = EventsServerOptions(accessToken, userAgent, null) - eventsService = EventsServiceProvider.provideEventsService(eventsServerOptions) telemetryService = TelemetryServiceProvider.provideTelemetryService(eventsServerOptions) - eventsService.registerObserver(eventsServiceObserver) + eventsManager.nativeEventsServiceInterface.registerObserver(eventsServiceObserver) } /** @@ -102,7 +111,7 @@ object MapboxMetricsReporter : MetricsReporter { fun disable() { isTelemetryInitialized = false removeObserver() - eventsService.unregisterObserver(eventsServiceObserver) + eventsManager.nativeEventsServiceInterface.unregisterObserver(eventsServiceObserver) ioJobController.job.cancelChildren() } @@ -118,8 +127,8 @@ object MapboxMetricsReporter : MetricsReporter { ) return } - eventsService.sendEvent( - Event(EventPriority.QUEUED, metricEvent.toValue(), null) + eventsManager.nativeEventsServiceInterface.sendEvent( + Event(eventsPriority, metricEvent.toValue(), null) ) { if (it != null) { logE("Failed to send event ${metricEvent.metricName}: $it", LOG_CATEGORY) @@ -137,11 +146,11 @@ object MapboxMetricsReporter : MetricsReporter { */ override fun sendTurnstileEvent(turnstileEvent: TurnstileEvent) { ifTelemetryIsRunning { - eventsService.sendTurnstileEvent(turnstileEvent) { - if (it != null) { - logE("Failed to send Turnstile event: $it", LOG_CATEGORY) - } - } +// eventsManager.nativeEventsServiceInterface.sendTurnstileEvent(turnstileEvent) { +// if (it != null) { +// logE("Failed to send Turnstile event: $it", LOG_CATEGORY) +// } +// } } } @@ -159,6 +168,20 @@ object MapboxMetricsReporter : MetricsReporter { this.metricsObserver = null } + /** + * Register [EventsServiceObserver] + */ + fun registerEventsServiceObserver(observer: EventsServiceObserver) { +// eventsManager.nativeEventsServiceInterface.registerObserver(observer) + } + + /** + * Unregister [EventsServiceObserver] + */ + fun unregisterEventsServiceObserver(observer: EventsServiceObserver) { +// eventsManager.nativeEventsServiceInterface.unregisterObserver(observer) + } + private inline fun ifTelemetryIsRunning(func: () -> Unit) { if (isTelemetryInitialized && TelemetryUtilsDelegate.getEventsCollectionState()) { func.invoke() diff --git a/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/EventsObserver.kt b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/EventsObserver.kt new file mode 100644 index 00000000000..f006de47cb8 --- /dev/null +++ b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/EventsObserver.kt @@ -0,0 +1,7 @@ +package com.mapbox.navigation.metrics.events + +import com.mapbox.bindgen.Value + +fun interface EventsObserver { + fun onEvents(events: Value) +} diff --git a/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/EventsServiceInterfacesManager.kt b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/EventsServiceInterfacesManager.kt new file mode 100644 index 00000000000..d4818659d97 --- /dev/null +++ b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/EventsServiceInterfacesManager.kt @@ -0,0 +1,50 @@ +package com.mapbox.navigation.metrics.events + +import com.mapbox.bindgen.Value +import com.mapbox.common.EventsServiceError +import com.mapbox.common.EventsServiceInterface +import com.mapbox.common.EventsServiceObserver + +class EventsServiceInterfacesManager( +// platformEventsServiceInterface: EventsServiceInterface, + internal val nativeEventsServiceInterface: EventsServiceInterface, +) { + private val eventServices = listOf( +// platformEventsServiceInterface, + nativeEventsServiceInterface, + ) + + private val observers = mutableListOf() + + private val eventsServiceObserver = object : EventsServiceObserver { + override fun didEncounterError(error: EventsServiceError, events: Value) { + // do nothing + } + + override fun didSendEvents(events: Value) { + observers.forEach { it.onEvents(events) } + } + } + + fun registerEventsObserver(eventsObserver: EventsObserver) { + checkAndRegisterInternal() + observers.add(eventsObserver) + } + + fun unregisterEventsObserver(eventsObserver: EventsObserver) { + observers.remove(eventsObserver) + checkAndUnregisterInternal() + } + + private fun checkAndRegisterInternal() { + if (observers.isEmpty()) { + eventServices.forEach { it.registerObserver(eventsServiceObserver) } + } + } + + private fun checkAndUnregisterInternal() { + if (observers.isEmpty()) { + eventServices.forEach { it.unregisterObserver(eventsServiceObserver) } + } + } +} diff --git a/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/TelemetryEventsProvider.kt b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/TelemetryEventsProvider.kt new file mode 100644 index 00000000000..c7507eeedef --- /dev/null +++ b/libnavigation-metrics/src/main/java/com/mapbox/navigation/metrics/events/TelemetryEventsProvider.kt @@ -0,0 +1,16 @@ +package com.mapbox.navigation.metrics.events + +import com.mapbox.common.EventsServerOptions +import com.mapbox.common.EventsService + +object TelemetryEventsProvider { + fun getOrCreateEventsServiceInterfacesManager( + accessToken: String, + userAgent: String, + ): EventsServiceInterfacesManager = + EventsServiceInterfacesManager( + EventsService.getOrCreate( + EventsServerOptions(accessToken, userAgent, null) + ) + ) +} diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigator.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigator.kt index 06b1245d9cc..122a1918435 100644 --- a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigator.kt +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigator.kt @@ -11,6 +11,7 @@ import com.mapbox.navigation.base.route.NavigationRoute import com.mapbox.navigator.CacheHandle import com.mapbox.navigator.ConfigHandle import com.mapbox.navigator.ElectronicHorizonObserver +import com.mapbox.navigator.EventsMetadataInterface import com.mapbox.navigator.Experimental import com.mapbox.navigator.FallbackVersionsObserver import com.mapbox.navigator.FixLocation @@ -27,6 +28,7 @@ import com.mapbox.navigator.RouteAlternativesControllerInterface import com.mapbox.navigator.RouterInterface import com.mapbox.navigator.SetRoutesReason import com.mapbox.navigator.SetRoutesResult +import com.mapbox.navigator.Telemetry import com.mapbox.navigator.TilesConfig /** @@ -43,6 +45,7 @@ interface MapboxNativeNavigator { tilesConfig: TilesConfig, accessToken: String, router: RouterInterface, + eventsMetadataInterface: EventsMetadataInterface, ): MapboxNativeNavigator /** @@ -54,6 +57,7 @@ interface MapboxNativeNavigator { tilesConfig: TilesConfig, accessToken: String, router: RouterInterface, + eventsMetadataInterface: EventsMetadataInterface, ) suspend fun resetRideSession() @@ -194,4 +198,9 @@ interface MapboxNativeNavigator { val experimental: Experimental val router: RouterInterface + + /** + * Provide the Telemetry events interface instance. + */ + val telemetry: Telemetry } diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigatorImpl.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigatorImpl.kt index 7eb61dbdfad..6da55c3b778 100644 --- a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigatorImpl.kt +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/MapboxNativeNavigatorImpl.kt @@ -22,6 +22,7 @@ import com.mapbox.navigator.CacheDataDomain import com.mapbox.navigator.CacheHandle import com.mapbox.navigator.ConfigHandle import com.mapbox.navigator.ElectronicHorizonObserver +import com.mapbox.navigator.EventsMetadataInterface import com.mapbox.navigator.Experimental import com.mapbox.navigator.FallbackVersionsObserver import com.mapbox.navigator.FixLocation @@ -43,6 +44,7 @@ import com.mapbox.navigator.RouterInterface import com.mapbox.navigator.SetRoutesParams import com.mapbox.navigator.SetRoutesReason import com.mapbox.navigator.SetRoutesResult +import com.mapbox.navigator.Telemetry import com.mapbox.navigator.TilesConfig import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext @@ -67,6 +69,7 @@ object MapboxNativeNavigatorImpl : MapboxNativeNavigator { override lateinit var experimental: Experimental override lateinit var cache: CacheHandle override lateinit var router: RouterInterface + override lateinit var telemetry: Telemetry override lateinit var routeAlternativesController: RouteAlternativesControllerInterface private val nativeNavigatorRecreationObservers = CopyOnWriteArraySet() @@ -84,6 +87,7 @@ object MapboxNativeNavigatorImpl : MapboxNativeNavigator { tilesConfig: TilesConfig, accessToken: String, router: RouterInterface, + eventsMetadataInterface: EventsMetadataInterface, ): MapboxNativeNavigator { navigator?.shutdown() @@ -100,6 +104,7 @@ object MapboxNativeNavigatorImpl : MapboxNativeNavigator { experimental = nativeComponents.navigator.experimental cache = nativeComponents.cache this.router = nativeComponents.router + this.telemetry = nativeComponents.navigator.getTelemetry(eventsMetadataInterface) routeAlternativesController = nativeComponents.routeAlternativesController this.accessToken = accessToken return this @@ -113,10 +118,11 @@ object MapboxNativeNavigatorImpl : MapboxNativeNavigator { historyRecorderComposite: HistoryRecorderHandle?, tilesConfig: TilesConfig, accessToken: String, - router: RouterInterface + router: RouterInterface, + eventsMetadataInterface: EventsMetadataInterface, ) { val storeNavSessionState = navigator!!.storeNavigationSession() - create(config, historyRecorderComposite, tilesConfig, accessToken, router) + create(config, historyRecorderComposite, tilesConfig, accessToken, router, eventsMetadataInterface) navigator!!.restoreNavigationSession(storeNavSessionState) nativeNavigatorRecreationObservers.forEach { it.onNativeNavigatorRecreated() diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/NavigatorLoader.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/NavigatorLoader.kt index aec20a8879f..02eccbd2e4b 100644 --- a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/NavigatorLoader.kt +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/NavigatorLoader.kt @@ -20,6 +20,8 @@ import com.mapbox.navigator.RouterInterface import com.mapbox.navigator.RouterType import com.mapbox.navigator.SettingsProfile import com.mapbox.navigator.TilesConfig +import org.json.JSONException +import org.json.JSONObject /** * This class is expected to gain more responsibility as we define [customConfig]. @@ -34,7 +36,7 @@ object NavigatorLoader { ConfigFactory.build( settingsProfile(deviceProfile), navigatorConfig, - deviceProfile.customConfig, + enableNativeTelemetryEventsAndSwitchToImmediatePriority(deviceProfile.customConfig), ) fun createHistoryRecorderHandles( @@ -152,4 +154,40 @@ object NavigatorLoader { val router: RouterInterface, val routeAlternativesController: RouteAlternativesControllerInterface, ) + + private fun enableNativeTelemetryEventsAndSwitchToImmediatePriority(config: String): String { + val rootJsonObj = if (config.isNotBlank()) { + try { + JSONObject(config) + } catch (e: JSONException) { + logE(msg = "custom config json does not valid: $e") + logE(msg = "custom config: [$config]") + JSONObject() + } + } else { + JSONObject() + } + + val featuresJsonObj = if (rootJsonObj.has("features")) { + rootJsonObj.getJSONObject("features") + } else { + JSONObject().also { + rootJsonObj.put("features", it) + } + } + + featuresJsonObj.put("useTelemetryNavigationEvents", true) + + val telemetryJsonObj = if (rootJsonObj.has("telemetry")) { + rootJsonObj.getJSONObject("telemetry") + } else { + JSONObject().also { + rootJsonObj.put("telemetry", it) + } + } + + telemetryJsonObj.put("eventsPriority", "Immediate") + + return rootJsonObj.toString() + } } diff --git a/libtesting-ui/src/main/java/com/mapbox/navigation/testing/ui/utils/coroutines/Adapters.kt b/libtesting-ui/src/main/java/com/mapbox/navigation/testing/ui/utils/coroutines/Adapters.kt index 21e94c282ae..744c74a50a0 100644 --- a/libtesting-ui/src/main/java/com/mapbox/navigation/testing/ui/utils/coroutines/Adapters.kt +++ b/libtesting-ui/src/main/java/com/mapbox/navigation/testing/ui/utils/coroutines/Adapters.kt @@ -8,6 +8,7 @@ import com.mapbox.api.directions.v5.models.BannerInstructions import com.mapbox.api.directions.v5.models.RouteOptions import com.mapbox.api.directions.v5.models.VoiceInstructions import com.mapbox.bindgen.Expected +import com.mapbox.geojson.Point import com.mapbox.maps.MapboxMap import com.mapbox.maps.Style import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI @@ -41,10 +42,16 @@ import com.mapbox.navigation.ui.maps.route.line.api.RoutesRenderedResult import com.mapbox.navigation.ui.maps.route.line.model.RouteLineClearValue import com.mapbox.navigation.ui.maps.route.line.model.RouteLineError import com.mapbox.navigation.ui.maps.route.line.model.RouteSetValue +import com.mapbox.navigation.utils.internal.toPoint +import com.mapbox.turf.TurfConstants +import com.mapbox.turf.TurfMeasurement import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.runningFold +import kotlinx.coroutines.flow.sample import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -115,6 +122,21 @@ fun MapboxNavigation.rawLocationUpdates(): Flow { ) } +fun Flow.passed(meters: Double): Flow { + return runningFold(mutableListOf()) { acc, location -> + acc.apply { + add(location.toPoint()) + } + } + .sample(periodMillis = 500) + .map { points -> + TurfMeasurement.length(points, TurfConstants.UNIT_METERS) + } + .map { passedDistance -> + meters <= passedDistance + } +} + sealed interface NavigationRouteAlternativesResult { data class OnRouteAlternatives( val routeProgress: RouteProgress, diff --git a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/FeedbackActivity.kt b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/FeedbackActivity.kt index d13347362f0..9c47644712b 100644 --- a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/FeedbackActivity.kt +++ b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/FeedbackActivity.kt @@ -36,6 +36,7 @@ import com.mapbox.navigation.core.telemetry.events.FeedbackEvent import com.mapbox.navigation.core.telemetry.events.FeedbackHelper import com.mapbox.navigation.core.telemetry.events.FeedbackMetadata import com.mapbox.navigation.core.telemetry.events.FeedbackMetadataWrapper +import com.mapbox.navigation.core.telemetry.events.UserFeedback import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.LocationObserver import com.mapbox.navigation.core.trip.session.TripSessionState @@ -245,12 +246,13 @@ class FeedbackActivity : AppCompatActivity() { return@let } mapboxNavigation.postUserFeedback( - FeedbackEvent.POSITIONING_ISSUE, - "Test feedback", - FeedbackEvent.UI, - screenshot, - emptyArray(), - feedbackMetadata + UserFeedback.Builder( + FeedbackEvent.POSITIONING_ISSUE, + "Test feedback", + ) + .feedbackMetadata(feedbackMetadata) + .build() + ) Toast.makeText( this,