From e14f29a463a2f452d3566f0daa3fe55a9ef0e48e Mon Sep 17 00:00:00 2001 From: Kyle Madsen Date: Thu, 25 Aug 2022 18:18:22 -0700 Subject: [PATCH] Add mapbox navigation dump cli framework --- CHANGELOG.md | 1 + .../core/internal/dump/HelpDumpInterceptor.kt | 98 +++++++++++++ .../core/internal/dump/MapboxDumpHandler.kt | 44 ++++++ .../internal/dump/MapboxDumpInterceptor.kt | 52 +++++++ .../core/internal/dump/MapboxDumpRegistry.kt | 43 ++++++ .../dump/MapboxDumpRegistryDelegate.kt | 34 +++++ .../service/NavigationNotificationService.kt | 20 +++ .../internal/dump/MapboxDumpHandlerTest.kt | 134 ++++++++++++++++++ .../dump/MapboxDumpRegistryDelegateTest.kt | 71 ++++++++++ .../dump/DistanceFormatterDumpInterceptor.kt | 59 ++++++++ .../dump/NavigationViewApiDumpInterceptor.kt | 40 ++++++ .../MapboxNavigationViewCustomizedActivity.kt | 35 +++++ 12 files changed, 631 insertions(+) create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/HelpDumpInterceptor.kt create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandler.kt create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpInterceptor.kt create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistry.kt create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegate.kt create mode 100644 libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandlerTest.kt create mode 100644 libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegateTest.kt create mode 100644 qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/DistanceFormatterDumpInterceptor.kt create mode 100644 qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/NavigationViewApiDumpInterceptor.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6942b9c2c13..9b030c295ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Mapbox welcomes participation and contributions from everyone. - Introduced `NavigationViewApi#onManeuverCollapsed` and `NavigationViewApi#onManeuverExpanded` callback which is triggered upon changes to `MapboxManeuverViewState`. [#6286](https://github.com/mapbox/mapbox-navigation-android/pull/6286) - Implemented logic that would display `BannerComponents` of `type` `GuidanceView` and `subType` `BannerComponents#SAPA`, `BannerComponents#CITYREAL`, `BannerComponents#AFTERTOLL`, `BannerComponents#SIGNBOARD`, `BannerComponents#TOLLBRANCH`, `BannerComponents#EXPRESSWAY_ENTRANCE`, `BannerComponents#EXPRESSWAY_EXIT` using `JunctionViewApi`. [#6285](https://github.com/mapbox/mapbox-navigation-android/pull/6285) - Calling `MapboxNavigationApp.setup` will create a new `MapboxNavigation` instance with new `NavigationOptions` even if the app has been setup. [#6285](https://github.com/mapbox/mapbox-navigation-android/pull/6285) +- Added `MapboxDumpRegistry` which allows you to define adb commands for the sdk. [#6234](https://github.com/mapbox/mapbox-navigation-android/pull/6234) #### Bug fixes and improvements - Fixed an issue with `NavigationView` that caused overview camera to have wrong pitch. [#6278](https://github.com/mapbox/mapbox-navigation-android/pull/6278) - Fixed an issue with `NavigationView` that caused camera issues after reroute or switching to an alternative route. [#6283](https://github.com/mapbox/mapbox-navigation-android/pull/6283) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/HelpDumpInterceptor.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/HelpDumpInterceptor.kt new file mode 100644 index 00000000000..5554b655b93 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/HelpDumpInterceptor.kt @@ -0,0 +1,98 @@ +package com.mapbox.navigation.core.internal.dump + +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * Default interceptor which provides help for the adb service interface. + */ +class HelpDumpInterceptor : MapboxDumpInterceptor { + override fun command() = "help" + + override fun description(): String = "Shows available commands and instructions" + + override fun availableCommands(): List> { + return MapboxDumpRegistry.getInterceptors().filter { it != this }.map { + Pair( + "${command()}:${it.command()}", + "Get the commands available from ${it.command()}" + ) + } + } + + override fun intercept( + fileDescriptor: FileDescriptor, + writer: PrintWriter, + commands: List + ) { + if (commands.isEmpty()) { + printHelpFullDescription(writer) + } else if (commands.size == 1 && commands[0] == command()) { + printHelpCommandList(writer) + } else { + commands.forEach { command -> + val interceptorCommand = command.substringAfter("${command()}:") + val interceptors = MapboxDumpRegistry.getInterceptors(interceptorCommand) + if (interceptors.isEmpty()) { + writer.println("Could not find $command") + } else { + writer.println("Available commands for $command") + interceptors.forEach { interceptor -> + interceptor.availableCommands().forEach { + writer.println(" ${it.prettyString()}") + } + } + } + } + } + } + + private fun List>.prettyString() = joinToString( + separator = System.lineSeparator(), + transform = { it.prettyString() } + ) + + private fun Pair.prettyString() = "$first, $second" + + private fun printHelpFullDescription(writer: PrintWriter) { + writer.println( + """ +Hello and welcome to the Mapbox Navigation dump! + This allows you to control Mapbox Navigation + from adb. Below are the commands and shortcuts + that are available. If you'd like to create your + own commands, look at the `MapboxDumpRegistry`. + +Command arguments can be passed as key:value and are separated by spaces. + For example, if you pass data to dumpsys + and you have added a `MapboxDumpInterceptor`, your + interceptor will receive the command and the data. + + $ adb shell dumpsys activity service turn_off_audio_guidance + >> turn_off_audio_guidance + + $ adb shell dumpsys activity service months:june months:july + >> months:june months:july + + $ adb shell dumpsys activity service "animal":{"age":4,"name":"cat","weight":{"units":"kilograms","value":4.5}} + >> args[0] = animal:age:4 + >> args[1] = animal:name:cat + >> args[2] = animal:weight:units:kilograms + >> args[3] = animal:weight:value:4.5 + + Warning: json format may give unexpected results because arguments are split by spaces. + $ adb shell dumpsys activity service "name":"big cat" + >> args[0] = name:big + >> args[1] = cat + +Request help for the commands available. This list is given with the `help` command. +${availableCommands().prettyString()} + """.trimIndent() + ) + } + + private fun printHelpCommandList(writer: PrintWriter) { + writer.println("Request help for the commands available") + writer.println(availableCommands().prettyString()) + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandler.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandler.kt new file mode 100644 index 00000000000..ed9b5c73e7b --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandler.kt @@ -0,0 +1,44 @@ +package com.mapbox.navigation.core.internal.dump + +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * This will process commands from the dumpsys. Use the [MapboxDumpRegistry] to control what will + * happen when the dump command is called. + * + * @see MapboxDumpRegistry registry for interacting with the interceptors + */ +internal class MapboxDumpHandler { + + fun handle(fd: FileDescriptor, writer: PrintWriter, args: Array?) { + val handled = handleArguments(fd, writer, args) + if (handled.isEmpty()) { + // Call the default command when no interceptors are recognized. + MapboxDumpRegistry.defaultInterceptor?.intercept(fd, writer, emptyList()) + } + } + + private fun handleArguments( + fd: FileDescriptor, + writer: PrintWriter, + args: Array? + ): List { + val matches: List, List>> = args + ?.groupBy { it.substringBefore(":", it) } + ?.map { Pair(MapboxDumpRegistry.getInterceptors(it.key), it.value) } + ?: return emptyList() + + matches.forEach { match -> + val interceptors = match.first + val commands = match.second + if (interceptors.isEmpty()) { + writer.println("Unrecognized commands: ${commands.joinToString()}") + } else { + interceptors.forEach { it.intercept(fd, writer, commands) } + writer.println("Processed: ${commands.joinToString()}") + } + } + return matches.flatMap { it.first } + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpInterceptor.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpInterceptor.kt new file mode 100644 index 00000000000..98f617de16c --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpInterceptor.kt @@ -0,0 +1,52 @@ +package com.mapbox.navigation.core.internal.dump + +import android.app.Service +import com.google.gson.JsonSyntaxException +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * This is an interface that allows you to intercept command line arguments from dumpsys. + * + * All arguments beginning with `your_command:*` will be sent to the [intercept] function. + * + * For example, if you have an interceptor called "nav_feature". + * service=com.mapbox.navigation.core.trip.service.NavigationNotificationService + * $ adb shell dumpsys activity service ${service} nav_feature:one nav_feature:two + * The intercept function will receive listOf("nav_feature:one", "nav_feature:two") + */ +interface MapboxDumpInterceptor { + /** + * String representing the type of commands this interceptor can handle. + */ + fun command(): String + + /** + * Human readable description of what this interceptor is going to do when the command is + * passed. + * + * All available commands can be viewed with the `help` command + * $ adb shell dumpsys activity service {service-path} help + */ + fun description(): String + + /** + * Human readable list of each command and a description of what the command will do. This will + * be displayed with the `help:$command` + * + * @return pair of strings where the first is the `command` and the second is a `description`. + */ + fun availableCommands(): List> + + /** + * When a command is found by [Service.dump], the result will be passed to this interceptor. + * + * @throws JsonSyntaxException the caller is expected to handle json exceptions. + */ + @Throws(JsonSyntaxException::class) + fun intercept( + fileDescriptor: FileDescriptor, + writer: PrintWriter, + commands: List + ) +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistry.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistry.kt new file mode 100644 index 00000000000..9e48011f336 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistry.kt @@ -0,0 +1,43 @@ +package com.mapbox.navigation.core.internal.dump + +/** + * This is a singleton that allows downstream solutions to define their own interceptors. + */ +object MapboxDumpRegistry { + private val delegate = MapboxDumpRegistryDelegate() + + /** + * The default interceptor is usually the [HelpDumpInterceptor]. If you decide to override + * this, note that it will receive the empty command. + */ + var defaultInterceptor: MapboxDumpInterceptor? + get() = delegate.defaultInterceptor + set(value) { + delegate.defaultInterceptor = value + } + + /** + * Add interceptors. + */ + fun addInterceptors(vararg interceptors: MapboxDumpInterceptor) = + delegate.addInterceptors(*interceptors) + + /** + * Get all available interceptors. + */ + fun getInterceptors(): List = + delegate.getInterceptors() + + /** + * Get all available interceptors that can handle the [command]. + */ + fun getInterceptors(command: String): List = + delegate.getInterceptors(command) + + /** + * Remove interceptors. Note that this can remove the [defaultInterceptor] if you choose to + * use [getInterceptors] to [removeInterceptors]. + */ + fun removeInterceptors(vararg interceptors: MapboxDumpInterceptor) = + delegate.removeInterceptors(*interceptors) +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegate.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegate.kt new file mode 100644 index 00000000000..cb2acebbda3 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegate.kt @@ -0,0 +1,34 @@ +package com.mapbox.navigation.core.internal.dump + +/** + * This is used for unit testing the [MapboxDumpRegistry] singleton. + */ +internal class MapboxDumpRegistryDelegate { + private val interceptors = mutableSetOf() + + var defaultInterceptor: MapboxDumpInterceptor? = null + set(value) { + field?.let { this.interceptors.remove(it) } + value?.let { addInterceptors(it) } + field = value + } + + init { + defaultInterceptor = HelpDumpInterceptor() + } + + fun addInterceptors(vararg interceptors: MapboxDumpInterceptor) = + this.interceptors.addAll(interceptors) + + fun getInterceptors(): List = interceptors.toList() + + fun getInterceptors(command: String): List = + interceptors.filter { it.command() == command } + + fun removeInterceptors(vararg interceptors: MapboxDumpInterceptor) { + if (interceptors.contains(defaultInterceptor)) { + defaultInterceptor = null + } + this.interceptors.removeAll(interceptors.toSet()) + } +} 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 2c94361177c..302a255a7c9 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 @@ -5,8 +5,13 @@ import android.app.Service import android.content.Context import android.content.Intent import android.os.IBinder +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 /** * Service is updating information about current trip @@ -18,6 +23,13 @@ internal class NavigationNotificationService : Service() { startForeground(notificationResponse.notificationId, notificationResponse.notification) } + /** + * This will handle commands from `adb shell dumpsys activity service`. + * + * Use the [MapboxDumpRegistry] to add or remove dump interceptors. + */ + private val mapboxDumpHandler = MapboxDumpHandler() + /** * Return the communication channel to the service (always *null*) */ @@ -47,4 +59,12 @@ internal class NavigationNotificationService : Service() { ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) MapboxTripService.unregisterOneTimeNotificationDataObserver(notificationDataObserver) } + + /** + * Overrides the dump command so that state can be changed with adb. + */ + @CallSuper + override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array?) { + mapboxDumpHandler.handle(fd, writer, args) + } } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandlerTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandlerTest.kt new file mode 100644 index 00000000000..1f4ee67efe9 --- /dev/null +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpHandlerTest.kt @@ -0,0 +1,134 @@ +package com.mapbox.navigation.core.internal.dump + +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.runs +import io.mockk.unmockkAll +import io.mockk.verify +import io.mockk.verifyOrder +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.FileDescriptor +import java.io.PrintWriter + +class MapboxDumpHandlerTest { + + private val fd: FileDescriptor = mockk() + private val writer: PrintWriter = mockk(relaxed = true) + private val sut = MapboxDumpHandler() + + @Before + fun setup() { + mockkObject(MapboxDumpRegistry) + } + + @After + fun teardown() { + unmockkAll() + } + + @Test + fun `should use default interceptor when the command is not recognized`() { + val defaultInterceptor: MapboxDumpInterceptor = mockk(relaxed = true) { + every { command() } returns "help" + } + every { MapboxDumpRegistry.defaultInterceptor } returns defaultInterceptor + every { MapboxDumpRegistry.getInterceptors("test_command") } returns emptyList() + + sut.handle(fd, writer, arrayOf("test_command")) + + verify { defaultInterceptor.intercept(any(), any(), listOf()) } + } + + @Test + fun `should handle a basic command`() { + val interceptor = mockk { + every { command() } returns "test_command" + every { intercept(any(), any(), any()) } just runs + } + every { + MapboxDumpRegistry.getInterceptors("test_command") + } returns listOf(interceptor) + + sut.handle(fd, writer, arrayOf("test_command")) + + verify { interceptor.intercept(any(), any(), listOf("test_command")) } + } + + @Test + fun `should call intercept on interceptors with the command`() { + val interceptorOne = mockk { + every { command() } returns "test_command" + every { intercept(any(), any(), any()) } just runs + } + val interceptorTwo = mockk { + every { command() } returns "not_called" + every { intercept(any(), any(), any()) } just runs + } + val interceptorThree = mockk { + every { command() } returns "test_command" + every { intercept(any(), any(), any()) } just runs + } + every { MapboxDumpRegistry.getInterceptors("test_command") } returns listOf( + interceptorOne, interceptorThree + ) + every { MapboxDumpRegistry.getInterceptors("not_called") } returns listOf( + interceptorTwo + ) + + sut.handle(fd, writer, arrayOf("test_command")) + + verifyOrder { + interceptorOne.intercept(any(), any(), listOf("test_command")) + interceptorThree.intercept(any(), any(), listOf("test_command")) + } + verify(exactly = 0) { interceptorTwo.intercept(any(), any(), any()) } + } + + @Test + fun `should pass multiple arguments to interceptors`() { + val interceptorOne = mockk(relaxed = true) { + every { command() } returns "system" + every { intercept(any(), any(), any()) } just runs + } + val interceptorTwo = mockk { + every { command() } returns "feature" + every { intercept(any(), any(), any()) } just runs + } + val interceptorThree = mockk { + every { command() } returns "system" + every { intercept(any(), any(), any()) } just runs + } + every { MapboxDumpRegistry.getInterceptors("system") } returns listOf( + interceptorOne, interceptorThree + ) + every { MapboxDumpRegistry.getInterceptors("feature") } returns listOf( + interceptorTwo + ) + + val args = arrayOf( + "system", + "feature:longitude:-122.523667", + "feature:latitude:37.975391", + "system:argument", + "feature:replay:true", + "system:value:10", + ) + sut.handle(fd, writer, args) + + val expectedSystem = listOf("system", "system:argument", "system:value:10") + val expectedFeature = listOf( + "feature:longitude:-122.523667", + "feature:latitude:37.975391", + "feature:replay:true" + ) + verify { + interceptorOne.intercept(any(), any(), expectedSystem) + interceptorTwo.intercept(any(), any(), expectedFeature) + interceptorThree.intercept(any(), any(), expectedSystem) + } + } +} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegateTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegateTest.kt new file mode 100644 index 00000000000..35670e2d8af --- /dev/null +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/internal/dump/MapboxDumpRegistryDelegateTest.kt @@ -0,0 +1,71 @@ +package com.mapbox.navigation.core.internal.dump + +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * This is a singleton that allows downstream solutions to define their own interceptors. + */ +class MapboxDumpRegistryDelegateTest { + private val sut = MapboxDumpRegistryDelegate() + + @Test + fun `defaultInterceptor is a help message`() { + assertTrue(sut.defaultInterceptor is HelpDumpInterceptor) + } + + @Test + fun `getInterceptors will return the defaultInterceptor`() { + assertTrue(sut.getInterceptors().first() is HelpDumpInterceptor) + assertTrue(sut.getInterceptors("help").first() is HelpDumpInterceptor) + } + + @Test + fun `defaultInterceptor can be removed`() { + sut.defaultInterceptor = null + + assertTrue(sut.getInterceptors().isEmpty()) + } + + @Test + fun `removeInterceptors can remove the defaultInterceptor`() { + val helpInterceptor = sut.defaultInterceptor!! + sut.removeInterceptors(helpInterceptor) + + assertNull(sut.defaultInterceptor) + } + + @Test + fun `addInterceptors will result in new getInterceptors`() { + val interceptorOne = mockk() + val interceptorTwo = mockk() + + sut.addInterceptors(interceptorOne, interceptorTwo) + val interceptors = sut.getInterceptors() + + assertTrue(interceptors.containsAll(listOf(interceptorOne, interceptorTwo))) + } + + @Test + fun `getInterceptors can be filtered by command`() { + val interceptorOne = mockk { + every { command() } returns "system" + } + val interceptorTwo = mockk { + every { command() } returns "feature" + } + val interceptorThree = mockk { + every { command() } returns "system" + } + + sut.addInterceptors(interceptorOne, interceptorTwo, interceptorThree) + val featureInterceptors = sut.getInterceptors("feature") + val systemInterceptors = sut.getInterceptors("system") + + assertTrue(featureInterceptors.containsAll(listOf(interceptorTwo))) + assertTrue(systemInterceptors.containsAll(listOf(interceptorOne, interceptorThree))) + } +} diff --git a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/DistanceFormatterDumpInterceptor.kt b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/DistanceFormatterDumpInterceptor.kt new file mode 100644 index 00000000000..f9193c66232 --- /dev/null +++ b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/DistanceFormatterDumpInterceptor.kt @@ -0,0 +1,59 @@ +package com.mapbox.navigation.qa_test_app.dump + +import com.mapbox.navigation.base.formatter.UnitType +import com.mapbox.navigation.core.internal.dump.MapboxDumpInterceptor +import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp +import java.io.FileDescriptor +import java.io.PrintWriter + +class DistanceFormatterDumpInterceptor : MapboxDumpInterceptor { + override fun command() = COMMAND + + override fun description(): String = "Allows you to change the distance formatting options" + + override fun availableCommands(): List> = availableCommands + + override fun intercept( + fileDescriptor: FileDescriptor, + writer: PrintWriter, + commands: List + ) { + commands.forEach { command -> + when (command) { + COMMAND_UNIT_TYPE -> updateUnitType(writer, null) + COMMAND_UNIT_TYPE_METRIC -> updateUnitType(writer, UnitType.METRIC) + COMMAND_UNIT_TYPE_IMPERIAL -> updateUnitType(writer, UnitType.IMPERIAL) + } + } + } + + private fun updateUnitType(writer: PrintWriter, unitType: UnitType?) { + MapboxNavigationApp.current()?.navigationOptions?.let { currentOptions -> + if (currentOptions.distanceFormatterOptions.unitType != unitType) { + MapboxNavigationApp.disable() + MapboxNavigationApp.setup( + currentOptions.toBuilder().distanceFormatterOptions( + currentOptions.distanceFormatterOptions.toBuilder() + .unitType(unitType) + .build() + ).build() + ) + val currentUnitType = currentOptions.distanceFormatterOptions.unitType + writer.println("Updated unit type changed from $currentUnitType to $unitType") + } + } + } + + private companion object { + private const val COMMAND = "distance_formatter" + private const val COMMAND_UNIT_TYPE = "$COMMAND:unit_type" + private const val COMMAND_UNIT_TYPE_METRIC = "$COMMAND_UNIT_TYPE:metric" + private const val COMMAND_UNIT_TYPE_IMPERIAL = "$COMMAND_UNIT_TYPE:imperial" + + private val availableCommands = listOf( + Pair(COMMAND_UNIT_TYPE, "Use the device local unit type"), + Pair(COMMAND_UNIT_TYPE_METRIC, "Change to unit type to metric"), + Pair(COMMAND_UNIT_TYPE_IMPERIAL, "Change to unit type to imperial"), + ) + } +} diff --git a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/NavigationViewApiDumpInterceptor.kt b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/NavigationViewApiDumpInterceptor.kt new file mode 100644 index 00000000000..8b3795ac609 --- /dev/null +++ b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/dump/NavigationViewApiDumpInterceptor.kt @@ -0,0 +1,40 @@ +package com.mapbox.navigation.qa_test_app.dump + +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.internal.dump.MapboxDumpInterceptor +import com.mapbox.navigation.dropin.NavigationViewApi +import java.io.FileDescriptor +import java.io.PrintWriter + +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) +class NavigationViewApiDumpInterceptor( + private val navigationViewApi: NavigationViewApi +) : MapboxDumpInterceptor { + + override fun command(): String = COMMAND + + override fun description(): String = "Allows you to update navigation view state" + + override fun availableCommands(): List> = availableCommands + + override fun intercept( + fileDescriptor: FileDescriptor, + writer: PrintWriter, + commands: List + ) { + commands.forEach { command -> + when (command) { + COMMAND_START_FREE_DRIVE -> navigationViewApi.startFreeDrive() + } + } + } + + private companion object { + private const val COMMAND = "dropin" + private const val COMMAND_START_FREE_DRIVE = "$COMMAND:start_free_drive" + + private val availableCommands = listOf( + COMMAND_START_FREE_DRIVE to "Change the trip session state to free drive" + ) + } +} diff --git a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/customnavview/MapboxNavigationViewCustomizedActivity.kt b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/customnavview/MapboxNavigationViewCustomizedActivity.kt index aa314080ca3..7d7afb10c4f 100644 --- a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/customnavview/MapboxNavigationViewCustomizedActivity.kt +++ b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/customnavview/MapboxNavigationViewCustomizedActivity.kt @@ -25,6 +25,10 @@ import com.mapbox.maps.plugin.gestures.gestures import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.formatter.DistanceFormatterOptions import com.mapbox.navigation.base.formatter.UnitType +import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigation.core.internal.dump.MapboxDumpRegistry +import com.mapbox.navigation.core.internal.extensions.attachResumed +import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver import com.mapbox.navigation.dropin.ActionButtonDescription import com.mapbox.navigation.dropin.ActionButtonDescription.Position.END import com.mapbox.navigation.dropin.ActionButtonDescription.Position.START @@ -53,6 +57,8 @@ import com.mapbox.navigation.qa_test_app.R import com.mapbox.navigation.qa_test_app.databinding.LayoutActivityNavigationViewBinding import com.mapbox.navigation.qa_test_app.databinding.LayoutDrawerMenuNavViewCustomBinding import com.mapbox.navigation.qa_test_app.databinding.LayoutInfoPanelHeaderBinding +import com.mapbox.navigation.qa_test_app.dump.DistanceFormatterDumpInterceptor +import com.mapbox.navigation.qa_test_app.dump.NavigationViewApiDumpInterceptor import com.mapbox.navigation.qa_test_app.view.base.DrawerActivity import com.mapbox.navigation.ui.base.lifecycle.UIBinder import com.mapbox.navigation.ui.base.lifecycle.UIComponent @@ -119,6 +125,35 @@ class MapboxNavigationViewCustomizedActivity : DrawerActivity() { } } + /** + * This is a feature for Mapbox Navigation development. When the notification service is + * available you are able to send commands and handle state changes with dumpsys. + * + * $ adb shell dumpsys activity service com.mapbox.navigation.core.trip.service.NavigationNotificationService help + */ + private val dumpCommands = object : MapboxNavigationObserver { + + private val interceptors by lazy { + arrayOf( + DistanceFormatterDumpInterceptor(), + NavigationViewApiDumpInterceptor(binding.navigationView.api) + // Add your interceptors here + ) + } + + override fun onAttached(mapboxNavigation: MapboxNavigation) { + MapboxDumpRegistry.addInterceptors(*interceptors) + } + + override fun onDetached(mapboxNavigation: MapboxNavigation) { + MapboxDumpRegistry.removeInterceptors(*interceptors) + } + } + + init { + attachResumed(dumpCommands) + } + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) override fun onCreate(savedInstanceState: Bundle?) { updateTheme()