diff --git a/app/src/androidTest/java/me/hufman/androidautoidrive/InstrumentedTestNotificationApp.kt b/app/src/androidTest/java/me/hufman/androidautoidrive/InstrumentedTestNotificationApp.kt index 06affa515..afd3ff067 100644 --- a/app/src/androidTest/java/me/hufman/androidautoidrive/InstrumentedTestNotificationApp.kt +++ b/app/src/androidTest/java/me/hufman/androidautoidrive/InstrumentedTestNotificationApp.kt @@ -17,7 +17,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.* import me.hufman.androidautoidrive.notifications.CarNotification import me.hufman.androidautoidrive.notifications.CarNotificationControllerIntent -import me.hufman.androidautoidrive.carapp.notifications.PhoneNotifications +import me.hufman.androidautoidrive.carapp.notifications.NotificationApp import me.hufman.androidautoidrive.notifications.NotificationUpdaterControllerIntent import org.awaitility.Awaitility.await @@ -36,7 +36,7 @@ class InstrumentedTestNotificationApp { val appContext = InstrumentationRegistry.getInstrumentation().targetContext // prepare to listen to updates from the phone - val mockListener = mock {} + val mockListener = mock {} val updateListener = NotificationUpdaterControllerIntent.Receiver(mockListener) val updateReceiver = object: BroadcastReceiver() { override fun onReceive(p0: Context?, p1: Intent?) { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 12a802994..a14124004 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1066,6 +1066,11 @@ + + + + + diff --git a/app/src/main/java/me/hufman/androidautoidrive/carapp/ReadoutController.kt b/app/src/main/java/me/hufman/androidautoidrive/carapp/ReadoutController.kt index 6930f8cd5..916f36571 100644 --- a/app/src/main/java/me/hufman/androidautoidrive/carapp/ReadoutController.kt +++ b/app/src/main/java/me/hufman/androidautoidrive/carapp/ReadoutController.kt @@ -1,6 +1,12 @@ package me.hufman.androidautoidrive.carapp +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Handler import android.util.Log +import androidx.core.content.ContextCompat import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication import io.bimmergestalt.idriveconnectkit.rhmi.RHMIEvent import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModel @@ -26,22 +32,20 @@ enum class ReadoutState(val value: Int) { } } } -class ReadoutController(val name: String, val speechEvent: RHMIEvent.ActionEvent, val commandEvent: RHMIEvent.ActionEvent) { - val speechList = speechEvent.getAction()?.asLinkAction()?.getLinkModel()?.asRaListModel()!! - val commandList = commandEvent.getAction()?.asLinkAction()?.getLinkModel()?.asRaListModel()!! - companion object { - fun build(app: RHMIApplication, name: String): ReadoutController { - val events = app.events.values.filterIsInstance().filter { - it.getAction()?.asLinkAction()?.actionType == "readout" - } - if (events.size != 2) { - throw IllegalArgumentException("UI Description is missing 2 readout events") - } - return ReadoutController(name, events[0], events[1]) - } - } +interface ReadoutCommands { + fun readout(name: String, lines: Iterable) + fun cancel(name: String) +} +/** + * Implements a high-level wrapper around the car's Readout system + * A client would instantiate a ReadoutController with a given name and command interface + * and can trigger readout and cancel commands + * The client should also forward onTTSEvents to update the isActive flag, + * and the isActive flag shows that the car is currently reading out this named ReadoutController + */ +class ReadoutController(val name: String, val readoutCommands: ReadoutCommands) { var currentState: ReadoutState = ReadoutState.UNDEFINED var currentName: String = "" var currentBlock: Int? = null @@ -56,21 +60,102 @@ class ReadoutController(val name: String, val speechEvent: RHMIEvent.ActionEvent } fun readout(lines: Iterable) { - val data = RHMIModel.RaListModel.RHMIListConcrete(2) - data.addRow(arrayOf(lines.joinToString(".\n"), name)) - Log.d(TAG, "Starting readout from $name: ${data[0][0]}") - speechList.value = data - speechEvent.triggerEvent() +// Log.d(TAG, "Starting readout from $name") + readoutCommands.readout(name, lines) } fun cancel() { if (!isActive) { return } - Log.d(TAG, "Cancelling $name readout") +// Log.d(TAG, "Cancelling $name readout") + readoutCommands.cancel(name) + } +} + +class ReadoutCommandsRHMI(val speechEvent: RHMIEvent.ActionEvent, val commandEvent: RHMIEvent.ActionEvent): ReadoutCommands { + companion object { + fun build(app: RHMIApplication): ReadoutCommandsRHMI { + val events = app.events.values.filterIsInstance().filter { + it.getAction()?.asLinkAction()?.actionType == "readout" + } + if (events.size != 2) { + throw IllegalArgumentException("UI Description is missing 2 readout events") + } + return ReadoutCommandsRHMI(events[0], events[1]) + } + } + + val speechList = speechEvent.getAction()?.asLinkAction()?.getLinkModel()?.asRaListModel()!! + val commandList = commandEvent.getAction()?.asLinkAction()?.getLinkModel()?.asRaListModel()!! + + override fun readout(name: String, lines: Iterable) { + val data = RHMIModel.RaListModel.RHMIListConcrete(2) + data.addRow(arrayOf(lines.joinToString(".\n"), name)) + Log.d(TAG, "Starting rhmi readout from $name: ${data[0][0]}") + speechList.value = data + speechEvent.triggerEvent() + } + + override fun cancel(name: String) { + Log.d(TAG, "Cancelling rhmi readout from $name") val data = RHMIModel.RaListModel.RHMIListConcrete(2) data.addRow(arrayOf("STR_READOUT_STOP", name)) commandList.value = data commandEvent.triggerEvent() } +} + +class ReadoutCommandsSender(val context: Context): ReadoutCommands { + override fun readout(name: String, lines: Iterable) { + val intent = Intent(ReadoutCommandsReceiver.INTENT_READOUT) + .setPackage(context.packageName) + .putExtra(ReadoutCommandsReceiver.EXTRA_COMMAND, ReadoutCommandsReceiver.EXTRA_COMMAND_LINES) + .putExtra(ReadoutCommandsReceiver.EXTRA_COMMAND_NAME, name) + .putExtra(ReadoutCommandsReceiver.EXTRA_COMMAND_LINES, lines.toList().toTypedArray()) + context.sendBroadcast(intent) + } + + override fun cancel(name: String) { + val intent = Intent(ReadoutCommandsReceiver.INTENT_READOUT) + .setPackage(context.packageName) + .putExtra(ReadoutCommandsReceiver.EXTRA_COMMAND, ReadoutCommandsReceiver.EXTRA_COMMAND_CANCEL) + .putExtra(ReadoutCommandsReceiver.EXTRA_COMMAND_NAME, name) + context.sendBroadcast(intent) + } +} + +class ReadoutCommandsReceiver(val commands: ReadoutCommands): BroadcastReceiver() { + companion object { + const val INTENT_READOUT = "me.hufman.androidautoidrive.READOUT_COMMAND" + const val EXTRA_COMMAND = "READOUT" + const val EXTRA_COMMAND_NAME = "READOUT_NAME" + const val EXTRA_COMMAND_LINES = "READOUT_LINES" + const val EXTRA_COMMAND_CANCEL = "READOUT_CANCEL" + } + override fun onReceive(context: Context?, intent: Intent?) { + intent ?: return + val command = intent.getStringExtra(EXTRA_COMMAND) + val name = intent.getStringExtra(EXTRA_COMMAND_NAME) ?: return + val lines = intent.getStringArrayExtra(EXTRA_COMMAND_LINES) + if (command == EXTRA_COMMAND_LINES && lines != null) { + commands.readout(name, lines.toList()) + } + if (command == EXTRA_COMMAND_CANCEL) { + commands.cancel(name) + } + } + + fun register(context: Context, handler: Handler) { + ContextCompat.registerReceiver(context, this, IntentFilter(INTENT_READOUT), null, handler, ContextCompat.RECEIVER_NOT_EXPORTED) + } + + fun unregister(context: Context) { + try { + context.unregisterReceiver(this) + } catch (e: IllegalArgumentException) { + // duplicate unregister + } + } + } \ No newline at end of file diff --git a/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/ReadoutApp.kt b/app/src/main/java/me/hufman/androidautoidrive/carapp/carinfo/CarInfoApp.kt similarity index 76% rename from app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/ReadoutApp.kt rename to app/src/main/java/me/hufman/androidautoidrive/carapp/carinfo/CarInfoApp.kt index c2fa39adb..52ab4d563 100644 --- a/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/ReadoutApp.kt +++ b/app/src/main/java/me/hufman/androidautoidrive/carapp/carinfo/CarInfoApp.kt @@ -1,35 +1,54 @@ -package me.hufman.androidautoidrive.carapp.notifications +package me.hufman.androidautoidrive.carapp.carinfo import android.annotation.SuppressLint import android.content.res.Resources -import android.content.res.Resources.NotFoundException import android.os.Handler import android.util.Log -import com.google.gson.Gson import de.bmw.idrive.BMWRemoting import de.bmw.idrive.BMWRemotingServer import de.bmw.idrive.BaseBMWRemotingClient -import io.bimmergestalt.idriveconnectkit.CDS import io.bimmergestalt.idriveconnectkit.IDriveConnection import io.bimmergestalt.idriveconnectkit.RHMIUtils.rhmi_setResourceCached import io.bimmergestalt.idriveconnectkit.android.CarAppResources import io.bimmergestalt.idriveconnectkit.android.IDriveConnectionStatus import io.bimmergestalt.idriveconnectkit.android.security.SecurityAccess -import io.bimmergestalt.idriveconnectkit.rhmi.* +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplicationEtch +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplicationIdempotent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplicationSynchronized +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIEvent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIState import io.bimmergestalt.idriveconnectkit.rhmi.deserialization.loadFromXML import kotlinx.coroutines.android.asCoroutineDispatcher import me.hufman.androidautoidrive.AppSettings import me.hufman.androidautoidrive.BuildConfig import me.hufman.androidautoidrive.CarInformation import me.hufman.androidautoidrive.R -import me.hufman.androidautoidrive.carapp.* -import me.hufman.androidautoidrive.carapp.carinfo.CarDetailedInfo +import me.hufman.androidautoidrive.carapp.AMCategory +import me.hufman.androidautoidrive.carapp.FocusTriggerController +import me.hufman.androidautoidrive.carapp.L +import me.hufman.androidautoidrive.carapp.RHMIActionAbort +import me.hufman.androidautoidrive.carapp.RHMIApplicationSwappable +import me.hufman.androidautoidrive.carapp.ReadoutCommands +import me.hufman.androidautoidrive.carapp.ReadoutCommandsRHMI +import me.hufman.androidautoidrive.carapp.ReadoutController +import me.hufman.androidautoidrive.carapp.TTSState import me.hufman.androidautoidrive.carapp.carinfo.views.CarDetailedView import me.hufman.androidautoidrive.carapp.carinfo.views.CategoryView -import me.hufman.androidautoidrive.cds.* +import me.hufman.androidautoidrive.carapp.notifications.TAG +import me.hufman.androidautoidrive.cds.CDSConnectionEtch +import me.hufman.androidautoidrive.cds.CDSDataProvider +import me.hufman.androidautoidrive.cds.CDSEventHandler +import me.hufman.androidautoidrive.cds.CDSMetrics +import me.hufman.androidautoidrive.cds.flow +import me.hufman.androidautoidrive.cds.onPropertyChangedEvent +import me.hufman.androidautoidrive.cds.subscriptions import me.hufman.androidautoidrive.utils.Utils -class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securityAccess: SecurityAccess, val carAppAssets: CarAppResources, val unsignedCarAppAssets: CarAppResources, val handler: Handler, val resources: Resources, val appSettings: AppSettings) { +class CarInfoApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securityAccess: SecurityAccess, + val carAppAssets: CarAppResources, val unsignedCarAppAssets: CarAppResources, + val handler: Handler, val resources: Resources, val appSettings: AppSettings) { private val coroutineContext = handler.asCoroutineDispatcher() val carConnection: BMWRemotingServer var rhmiHandle: Int = -1 @@ -39,11 +58,11 @@ class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securit val focusTriggerController: FocusTriggerController val infoState: CarDetailedView val categoryState: CategoryView - val readoutController: ReadoutController + val readoutCommands: ReadoutCommands init { val cdsData = CDSDataProvider() - val listener = ReadoutAppListener(cdsData) + val listener = CarInfoAppListener(cdsData) carConnection = IDriveConnection.getEtchConnection(iDriveConnectionStatus.host ?: "127.0.0.1", iDriveConnectionStatus.port ?: 8003, listener) val readoutCert = carAppAssets.getAppCertificate(iDriveConnectionStatus.brand ?: "")?.readBytes() as ByteArray val sas_challenge = carConnection.sas_certificate(readoutCert) @@ -62,7 +81,7 @@ class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securit recreateRhmiApp() } - this.readoutController = ReadoutController.build(carApp, "NotificationReadout") + this.readoutCommands = ReadoutCommandsRHMI.build(carApp) val carInfo = CarInformation().also { cdsData.flow.defaultIntervalLimit = 100 @@ -76,20 +95,12 @@ class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securit initWidgets() } - // register for readout updates + // register for car info updates cdsData.setConnection(CDSConnectionEtch(carConnection)) - cdsData.subscriptions[CDS.HMI.TTS] = { - val state = try { - Gson().fromJson(it["TTSState"], TTSState::class.java) - } catch (e: Exception) { null } - if (state != null) { - readoutController.onTTSEvent(state) - } - } // set up the AM icon in the "Addressbook"/Communications section amHandle = carConnection.am_create("0", "\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0000".toByteArray()) - carConnection.am_addAppEventHandler(amHandle, "me.hufman.androidautoidrive.notification.readout") + carConnection.am_addAppEventHandler(amHandle, "me.hufman.androidautoidrive.notification.readout") // the old name, which might be in people's bookmarks createAmApp() } @@ -97,7 +108,7 @@ class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securit fun createRhmiApp(): RHMIApplication { // create the app in the car rhmiHandle = carConnection.rhmi_create(null, BMWRemoting.RHMIMetaData("me.hufman.androidautoidrive.notification.readout", BMWRemoting.VersionInfo(0, 1, 0), - "me.hufman.androidautoidrive.notification.readout", "me.hufman")) + "me.hufman.androidautoidrive.notification.readout", "me.hufman")) carConnection.rhmi_setResourceCached(rhmiHandle, BMWRemoting.RHMIResourceType.DESCRIPTION, carAppAssets.getUiDescription()) if (BuildConfig.SEND_UNSIGNED_RESOURCES) { try { @@ -142,16 +153,16 @@ class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securit } else { Utils.convertPngToGrayscale(resources.openRawResource(R.drawable.ic_carinfo_common).readBytes()) } - } catch (e: NotFoundException) { "" } + } catch (e: Resources.NotFoundException) { "" } val amInfo = mutableMapOf( - 0 to 145, // basecore version - 1 to name, // app name - 2 to carAppImage, - 3 to AMCategory.VEHICLE_INFORMATION.value, // section - 4 to true, - 5 to 800, // weight - 8 to infoState.state.id // mainstateId + 0 to 145, // basecore version + 1 to name, // app name + 2 to carAppImage, + 3 to AMCategory.VEHICLE_INFORMATION.value, // section + 4 to true, + 5 to 800, // weight + 8 to infoState.state.id // mainstateId ) // language translations, dunno which one is which for (languageCode in 101..123) { @@ -163,7 +174,7 @@ class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securit } } - inner class ReadoutAppListener(val cdsEventHandler: CDSEventHandler): BaseBMWRemotingClient() { + inner class CarInfoAppListener(val cdsEventHandler: CDSEventHandler): BaseBMWRemotingClient() { var server: BMWRemotingServer? = null var app: RHMIApplication? = null diff --git a/app/src/main/java/me/hufman/androidautoidrive/carapp/carinfo/CarInfoAppService.kt b/app/src/main/java/me/hufman/androidautoidrive/carapp/carinfo/CarInfoAppService.kt new file mode 100644 index 000000000..0c811c344 --- /dev/null +++ b/app/src/main/java/me/hufman/androidautoidrive/carapp/carinfo/CarInfoAppService.kt @@ -0,0 +1,36 @@ +package me.hufman.androidautoidrive.carapp.carinfo + +import android.util.Log +import io.bimmergestalt.idriveconnectkit.android.CarAppAssetResources +import me.hufman.androidautoidrive.AppSettingsViewer +import me.hufman.androidautoidrive.MainService +import me.hufman.androidautoidrive.carapp.CarAppService +import me.hufman.androidautoidrive.carapp.ReadoutCommandsReceiver + +class CarInfoAppService: CarAppService() { + + var carApp: CarInfoApp? = null + var readoutController: ReadoutCommandsReceiver? = null + + override fun shouldStartApp(): Boolean = true + + override fun onCarStart() { + Log.i(MainService.TAG, "Starting car info app") + + val handler = handler!! + val carApp = CarInfoApp(iDriveConnectionStatus, securityAccess, + CarAppAssetResources(applicationContext, "news"), + CarAppAssetResources(applicationContext, "carinfo_unsigned"), + handler, applicationContext.resources, AppSettingsViewer() + ) + this.carApp = carApp + readoutController = ReadoutCommandsReceiver(carApp.readoutCommands) + readoutController?.register(this, handler) + } + + override fun onCarStop() { + carApp?.disconnect() + carApp = null + readoutController?.unregister(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/PhoneNotifications.kt b/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationApp.kt similarity index 95% rename from app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/PhoneNotifications.kt rename to app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationApp.kt index 992098223..3a66515a0 100644 --- a/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/PhoneNotifications.kt +++ b/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationApp.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.os.Handler import android.util.Log +import com.google.gson.Gson import de.bmw.idrive.BMWRemoting import de.bmw.idrive.BMWRemotingServer import de.bmw.idrive.BaseBMWRemotingClient @@ -32,7 +33,7 @@ import java.util.* const val TAG = "PhoneNotifications" const val HMI_CONTEXT_THRESHOLD = 5000L -class PhoneNotifications(val iDriveConnectionStatus: IDriveConnectionStatus, val securityAccess: SecurityAccess, val carAppAssets: CarAppResources, val phoneAppResources: PhoneAppResources, val graphicsHelpers: GraphicsHelpers, val controller: CarNotificationController, val audioPlayer: AudioPlayer, val notificationSettings: NotificationSettings) { +class NotificationApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securityAccess: SecurityAccess, val carAppAssets: CarAppResources, val phoneAppResources: PhoneAppResources, val graphicsHelpers: GraphicsHelpers, val controller: CarNotificationController, val audioPlayer: AudioPlayer, val notificationSettings: NotificationSettings) { val notificationListener = PhoneNotificationListener(this) val notificationReceiver = NotificationUpdaterControllerIntent.Receiver(notificationListener) var notificationBroadcastReceiver: BroadcastReceiver? = null @@ -162,6 +163,15 @@ class PhoneNotifications(val iDriveConnectionStatus: IDriveConnectionStatus, val } } } catch (e: IOException) {} + // subscribe to TTS + cdsData.subscriptions[CDS.HMI.TTS] = { + val state = try { + Gson().fromJson(it["TTSState"], TTSState::class.java) + } catch (e: Exception) { null } + if (state != null) { + readoutInteractions.readoutController?.onTTSEvent(state) + } + } } } @@ -349,7 +359,7 @@ class PhoneNotifications(val iDriveConnectionStatus: IDriveConnectionStatus, val } /** All open, so that we can mock them in tests */ - open inner class PhoneNotificationListener(val phoneNotifications: PhoneNotifications): NotificationUpdaterController { + open inner class PhoneNotificationListener(val notificationApp: NotificationApp): NotificationUpdaterController { override fun onNewNotification(key: String) { val sbn = NotificationsState.getNotificationByKey(key) ?: return onNotification(sbn) diff --git a/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationAppService.kt b/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationAppService.kt index c826d0f2b..edac11acb 100644 --- a/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationAppService.kt +++ b/app/src/main/java/me/hufman/androidautoidrive/carapp/notifications/NotificationAppService.kt @@ -5,6 +5,8 @@ import android.util.Log import io.bimmergestalt.idriveconnectkit.android.CarAppAssetResources import me.hufman.androidautoidrive.* import me.hufman.androidautoidrive.carapp.CarAppService +import me.hufman.androidautoidrive.carapp.ReadoutCommandsSender +import me.hufman.androidautoidrive.carapp.ReadoutController import me.hufman.androidautoidrive.connections.BtStatus import me.hufman.androidautoidrive.notifications.AudioPlayer import me.hufman.androidautoidrive.notifications.CarNotificationControllerIntent @@ -13,8 +15,7 @@ import me.hufman.androidautoidrive.utils.GraphicsHelpersAndroid class NotificationAppService: CarAppService() { val appSettings = AppSettingsViewer() - var carappNotifications: PhoneNotifications? = null - var carappReadout: ReadoutApp? = null + var carappNotifications: NotificationApp? = null var carappStatusbar: ID5StatusbarApp? = null override fun shouldStartApp(): Boolean { @@ -26,7 +27,7 @@ class NotificationAppService: CarAppService() { val handler = handler!! val notificationSettings = NotificationSettings(carInformation.capabilities, BtStatus(applicationContext) {}, MutableAppSettingsReceiver(applicationContext, handler)) notificationSettings.btStatus.register() - carappNotifications = PhoneNotifications(iDriveConnectionStatus, securityAccess, + carappNotifications = NotificationApp(iDriveConnectionStatus, securityAccess, CarAppAssetResources(applicationContext, "basecoreOnlineServices"), PhoneAppResourcesAndroid(applicationContext), GraphicsHelpersAndroid(), @@ -34,6 +35,8 @@ class NotificationAppService: CarAppService() { AudioPlayer(applicationContext), notificationSettings) carappNotifications?.onCreate(applicationContext, handler) + carappNotifications?.readoutInteractions?.readoutController = ReadoutController("NotificationReadout", ReadoutCommandsSender(this)) + // request an initial draw // API24 can turn off the service, so we ask it to start up the service // The service automatically loads all the data onStart if the car is connected @@ -43,18 +46,6 @@ class NotificationAppService: CarAppService() { .setPackage(packageName) applicationContext.sendBroadcast(intent) - handler.post { - if (running) { - // start up the readout app - // using a handler to automatically handle shutting down during init - val carappReadout = ReadoutApp(iDriveConnectionStatus, securityAccess, - CarAppAssetResources(applicationContext, "news"), - CarAppAssetResources(applicationContext, "carinfo_unsigned"), - handler, applicationContext.resources, AppSettingsViewer()) - carappNotifications?.readoutInteractions?.readoutController = carappReadout.readoutController - this.carappReadout = carappReadout - } - } handler.post { val id4 = carInformation.capabilities["hmi.type"]?.contains("ID4") if (running && id4 == false) { @@ -83,8 +74,6 @@ class NotificationAppService: CarAppService() { NotificationListenerServiceImpl.shutdownService(this) carappNotifications?.disconnect() carappNotifications = null - carappReadout?.disconnect() - carappReadout = null carappStatusbar?.disconnect() carappStatusbar = null } diff --git a/app/src/test/java/me/hufman/androidautoidrive/carinfo/CarInfoAppTest.kt b/app/src/test/java/me/hufman/androidautoidrive/carinfo/CarInfoAppTest.kt new file mode 100644 index 000000000..5bb4449a0 --- /dev/null +++ b/app/src/test/java/me/hufman/androidautoidrive/carinfo/CarInfoAppTest.kt @@ -0,0 +1,52 @@ +package me.hufman.androidautoidrive.carinfo + +import android.content.res.Resources +import io.bimmergestalt.idriveconnectkit.IDriveConnection +import io.bimmergestalt.idriveconnectkit.android.CarAppResources +import io.bimmergestalt.idriveconnectkit.android.IDriveConnectionStatus +import io.bimmergestalt.idriveconnectkit.android.security.SecurityAccess +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent +import me.hufman.androidautoidrive.AppSettingsViewer +import me.hufman.androidautoidrive.MockBMWRemotingServer +import me.hufman.androidautoidrive.carapp.L +import me.hufman.androidautoidrive.carapp.carinfo.CarInfoApp +import org.junit.Assert +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import java.io.ByteArrayInputStream + +class CarInfoAppTest { + + val iDriveConnectionStatus = mock() + val securityAccess = mock { + on { signChallenge(any(), any() )} doReturn ByteArray(512) + } + val carAppResources = mock { + on { getAppCertificate() } doReturn ByteArrayInputStream(ByteArray(0)) + on { getUiDescription() } doAnswer { this.javaClass.classLoader!!.getResourceAsStream("ui_description_news.xml") } + on { getImagesDB(any()) } doReturn ByteArrayInputStream(ByteArray(0)) + on { getTextsDB(any()) } doReturn ByteArrayInputStream(ByteArray(0)) + } + val resources = mock { + on { openRawResource(any()) } doThrow Resources.NotFoundException() + } + + @Test + fun testAppInit() { + val mockServer = MockBMWRemotingServer() + IDriveConnection.mockRemotingServer = mockServer + val app = CarInfoApp(iDriveConnectionStatus, securityAccess, carAppResources, mock(), mock(), resources, AppSettingsViewer()) + + val labelComponent = app.infoState.state.componentsList.filterIsInstance().first() + Assert.assertEquals(L.CARINFO_TITLE, mockServer.data[labelComponent.model]) + val listComponent = app.infoState.state.componentsList.filterIsInstance().first() + // won't show data until the screen is opened + + app.disconnect() + } + +} \ No newline at end of file diff --git a/app/src/test/java/me/hufman/androidautoidrive/notifications/NotificationAppTest.kt b/app/src/test/java/me/hufman/androidautoidrive/notifications/NotificationAppTest.kt index 90a069c06..88e0768d9 100644 --- a/app/src/test/java/me/hufman/androidautoidrive/notifications/NotificationAppTest.kt +++ b/app/src/test/java/me/hufman/androidautoidrive/notifications/NotificationAppTest.kt @@ -26,7 +26,10 @@ import io.bimmergestalt.idriveconnectkit.rhmi.* import io.bimmergestalt.idriveconnectkit.rhmi.deserialization.loadFromXML import me.hufman.androidautoidrive.* import me.hufman.androidautoidrive.carapp.L +import me.hufman.androidautoidrive.carapp.ReadoutCommands +import me.hufman.androidautoidrive.carapp.ReadoutCommandsSender import me.hufman.androidautoidrive.carapp.ReadoutController +import me.hufman.androidautoidrive.carapp.ReadoutState import me.hufman.androidautoidrive.carapp.notifications.* import me.hufman.androidautoidrive.carapp.notifications.views.NotificationListView import me.hufman.androidautoidrive.utils.GraphicsHelpers @@ -122,7 +125,7 @@ class NotificationAppTest { fun testAppInit() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val mockClient = IDriveConnection.mockRemotingClient as BMWRemotingClient // test the AM button @@ -200,6 +203,56 @@ class NotificationAppTest { app.carappListener.cds_onPropertyChangedEvent(-1, "40", "driving.parkingBrake", """{"parkingBrake":2}""") assertEquals(false, mockServer.properties[app.viewDetails.state.id]?.get(36)) } + + assertTrue(mockServer.cdsSubscriptions.contains("hmi.tts")) + } + + @Test + fun testTTSCallback() { + val mockServer = MockBMWRemotingServer() + IDriveConnection.mockRemotingServer = mockServer + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + app.readoutInteractions.readoutController = ReadoutController("NotificationReadout", mock()) + + IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "113", "hmi.tts", + "{\"TTSState\": {\"state\": 0, \"type\": \"app\", \"currentblock\": 0}}" ) + assertEquals(ReadoutState.UNDEFINED, app.readoutInteractions.readoutController?.currentState) + assertEquals("app", app.readoutInteractions.readoutController?.currentName) + assertEquals(0, app.readoutInteractions.readoutController?.currentBlock) + + // test invalid data + IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "1", "hmi.tts", "{}") + } + + @Test + fun testTTSTrigger() { + val mockServer = MockBMWRemotingServer() + IDriveConnection.mockRemotingServer = mockServer + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val readoutCommands = mock() + app.readoutInteractions.readoutController = ReadoutController("NotificationReadout", readoutCommands) + + app.readoutInteractions.readoutController?.readout(listOf("Test Output")) + verify(readoutCommands).readout("NotificationReadout", listOf("Test Output")) + + // car updates the controller + IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "113", "hmi.tts", + "{\"TTSState\": {\"state\": 3, \"type\": \"NotificationReadout\", \"currentblock\": 0}}" ) + assertTrue(app.readoutInteractions.readoutController!!.isActive) + + // test cancel + app.readoutInteractions.readoutController?.cancel() + verify(readoutCommands).cancel("NotificationReadout") + reset(readoutCommands) + + // car updates the controller + IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "113", "hmi.tts", + "{\"TTSState\": {\"state\": 0, \"type\": \"NotificationReadout\", \"currentblock\": 0}}" ) + assertFalse(app.readoutInteractions.readoutController!!.isActive) + + // cancel shouldn't trigger another cancel + app.readoutInteractions.readoutController?.cancel() + verify(readoutCommands, never()).cancel("NotificationReadout") } @Suppress("DEPRECATION") @@ -470,7 +523,7 @@ class NotificationAppTest { fun testPopupStatusbar() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) app.readoutInteractions.readoutController = readoutController val bundle = createNotificationObject("Chat: Title", "Title: FirstLine\nTitle: Text") @@ -496,7 +549,7 @@ class NotificationAppTest { fun testPopupNewNotification() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) app.readoutInteractions.readoutController = readoutController val bundle = createNotificationObject("Chat: Title", "Title: FirstLine\nTitle: Text") @@ -531,7 +584,7 @@ class NotificationAppTest { fun testPopupUpdatedNotification() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) // posting the notification val bundle = createNotificationObject("Title", "Text") @@ -573,7 +626,7 @@ class NotificationAppTest { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) // it should not popup val bundle2 = createNotificationObject("Title", "Text") @@ -589,7 +642,7 @@ class NotificationAppTest { fun testPopupReadingNotification() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val bundle = createNotificationObject("Title", "Text") @@ -621,7 +674,7 @@ class NotificationAppTest { fun testPopupInputSuppress() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val bundle = createNotificationObject("Title", "Text") @@ -673,7 +726,7 @@ class NotificationAppTest { fun testPopupInteractionSuppress() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val bundle = createNotificationObject("Title", "Text") @@ -708,7 +761,7 @@ class NotificationAppTest { fun testDismissPopup() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) app.readoutInteractions.readoutController = readoutController val bundle = createNotificationObject("Title", "Text") @@ -745,7 +798,7 @@ class NotificationAppTest { fun testPopupNotificationHistoryClearing() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val bundle = createNotificationObject("Title", "Text") @@ -785,7 +838,7 @@ class NotificationAppTest { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) NotificationsState.notifications.clear() // on viewing the state, it should skip to the permissions view @@ -810,7 +863,7 @@ class NotificationAppTest { fun testViewEmptyNotifications() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) NotificationsState.notifications.clear() app.viewList.redrawNotificationList() @@ -828,7 +881,7 @@ class NotificationAppTest { fun testViewNotifications() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val item1 = createNotificationObject("Title", "Text") val item2 = createNotificationObject("Title2", "Text2\nLine2") @@ -923,7 +976,7 @@ class NotificationAppTest { fun testClickEntryButton() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) app.readoutInteractions.readoutController = readoutController val mockClient = IDriveConnection.mockRemotingClient as BMWRemotingClient @@ -982,7 +1035,7 @@ class NotificationAppTest { fun testClickNotification() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) app.readoutInteractions.readoutController = readoutController val notification = createNotificationObject("Title", "Text",false) @@ -1092,7 +1145,7 @@ class NotificationAppTest { fun testBookmarkNotificationMenu() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val callbacks = IDriveConnection.mockRemotingClient as BMWRemotingClient val notification = createNotificationObject("Title", "Text", false) @@ -1147,7 +1200,7 @@ class NotificationAppTest { fun testViewSidePicture() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val picture = mock { on { intrinsicWidth } doReturn 400 @@ -1175,7 +1228,7 @@ class NotificationAppTest { fun testViewReplyNotification() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) val actions = listOf( CarNotification.Action("Reply", true, listOf("Yes", "No")), @@ -1255,7 +1308,7 @@ class NotificationAppTest { fun testActionReadout() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) app.readoutInteractions.readoutController = readoutController whenever(notificationSettings.shouldReadoutNotificationDetails()) doReturn false @@ -1306,7 +1359,7 @@ class NotificationAppTest { val mockServer = spy(MockBMWRemotingServer()) IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) appSettings[AppSettings.KEYS.ENABLED_NOTIFICATIONS_POPUP] = "true" appSettings[AppSettings.KEYS.ENABLED_NOTIFICATIONS_POPUP_PASSENGER] = "false" @@ -1357,7 +1410,7 @@ class NotificationAppTest { fun testViewEmptyNotification() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) NotificationsState.notifications.clear() val notification = createNotificationObject("Title", "Text", false) @@ -1407,7 +1460,7 @@ class NotificationAppTest { fun testHideNotificationView() { val mockServer = MockBMWRemotingServer() IDriveConnection.mockRemotingServer = mockServer - val app = PhoneNotifications(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) + val app = NotificationApp(iDriveConnectionStatus, securityAccess, carAppResources, phoneAppResources, graphicsHelpers, carNotificationController, audioPlayer, notificationSettings) NotificationsState.notifications.clear() val notification = createNotificationObject("Title", "Text", false) diff --git a/app/src/test/java/me/hufman/androidautoidrive/notifications/ReadoutAppTest.kt b/app/src/test/java/me/hufman/androidautoidrive/notifications/ReadoutAppTest.kt deleted file mode 100644 index be51676ae..000000000 --- a/app/src/test/java/me/hufman/androidautoidrive/notifications/ReadoutAppTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package me.hufman.androidautoidrive.notifications - -import android.content.res.Resources -import org.mockito.kotlin.* -import de.bmw.idrive.BMWRemoting -import io.bimmergestalt.idriveconnectkit.IDriveConnection -import io.bimmergestalt.idriveconnectkit.android.CarAppResources -import io.bimmergestalt.idriveconnectkit.android.IDriveConnectionStatus -import io.bimmergestalt.idriveconnectkit.android.security.SecurityAccess -import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent -import me.hufman.androidautoidrive.AppSettingsViewer -import me.hufman.androidautoidrive.MockBMWRemotingServer -import me.hufman.androidautoidrive.carapp.L -import me.hufman.androidautoidrive.carapp.ReadoutState -import me.hufman.androidautoidrive.carapp.notifications.ReadoutApp -import org.junit.Assert.* -import org.junit.Test -import java.io.ByteArrayInputStream - -class ReadoutAppTest { - - val iDriveConnectionStatus = mock() - val securityAccess = mock { - on { signChallenge(any(), any() )} doReturn ByteArray(512) - } - val carAppResources = mock { - on { getAppCertificate() } doReturn ByteArrayInputStream(ByteArray(0)) - on { getUiDescription() } doAnswer { this.javaClass.classLoader!!.getResourceAsStream("ui_description_news.xml") } - on { getImagesDB(any()) } doReturn ByteArrayInputStream(ByteArray(0)) - on { getTextsDB(any()) } doReturn ByteArrayInputStream(ByteArray(0)) - } - val resources = mock { - on { openRawResource(any()) } doThrow Resources.NotFoundException() - } - - @Test - fun testAppInit() { - val mockServer = MockBMWRemotingServer() - IDriveConnection.mockRemotingServer = mockServer - val app = ReadoutApp(iDriveConnectionStatus, securityAccess, carAppResources, mock(), mock(), resources, AppSettingsViewer()) - - val labelComponent = app.infoState.state.componentsList.filterIsInstance().first() - assertEquals(L.CARINFO_TITLE, mockServer.data[labelComponent.model]) - val listComponent = app.infoState.state.componentsList.filterIsInstance().first() - // won't show data until the screen is opened - - assertTrue(mockServer.cdsSubscriptions.contains("hmi.tts")) - - app.disconnect() - } - - @Test - fun testTTSCallback() { - val mockServer = MockBMWRemotingServer() - IDriveConnection.mockRemotingServer = mockServer - val app = ReadoutApp(iDriveConnectionStatus, securityAccess, carAppResources, mock(), mock(), resources, AppSettingsViewer()) - - IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "113", "hmi.tts", - "{\"TTSState\": {\"state\": 0, \"type\": \"app\", \"currentblock\": 0}}" ) - assertEquals(ReadoutState.UNDEFINED, app.readoutController.currentState) - assertEquals("app", app.readoutController.currentName) - assertEquals(0, app.readoutController.currentBlock) - - // test invalid data - IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "1", "hmi.tts", "{}") - } - - @Test - fun testTTSTrigger() { - val mockServer = MockBMWRemotingServer() - IDriveConnection.mockRemotingServer = mockServer - val app = ReadoutApp(iDriveConnectionStatus, securityAccess, carAppResources, mock(), mock(), resources, AppSettingsViewer()) - - app.readoutController.readout(listOf("Test Output")) - val speechList = mockServer.data[app.readoutController.speechList.id] as BMWRemoting.RHMIDataTable - assertEquals("Test Output", speechList.data[0][0]) - assertEquals(mapOf(0 to null), mockServer.triggeredEvents[app.readoutController.speechEvent.id]) - - // car updates the controller - IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "113", "hmi.tts", - "{\"TTSState\": {\"state\": 3, \"type\": \"NotificationReadout\", \"currentblock\": 0}}" ) - assertTrue(app.readoutController.isActive) - - // test cancel - app.readoutController.cancel() - val commandList = mockServer.data[app.readoutController.commandList.id] as BMWRemoting.RHMIDataTable - assertEquals("STR_READOUT_STOP", commandList.data[0][0]) - assertEquals(mapOf(0 to null), mockServer.triggeredEvents[app.readoutController.commandEvent.id]) - - // car updates the controller - IDriveConnection.mockRemotingClient?.cds_onPropertyChangedEvent(1, "113", "hmi.tts", - "{\"TTSState\": {\"state\": 0, \"type\": \"NotificationReadout\", \"currentblock\": 0}}" ) - assertFalse(app.readoutController.isActive) - - // cancel shouldn't trigger another cancel - mockServer.data.clear() - mockServer.triggeredEvents.clear() - app.readoutController.cancel() - assertNull(mockServer.data[app.readoutController.commandList.id]) - assertNull(mockServer.triggeredEvents[app.readoutController.commandEvent.id]) - } -} \ No newline at end of file diff --git a/app/src/test/java/me/hufman/androidautoidrive/notifications/ReadoutControllerTest.kt b/app/src/test/java/me/hufman/androidautoidrive/notifications/ReadoutControllerTest.kt new file mode 100644 index 000000000..067ad7762 --- /dev/null +++ b/app/src/test/java/me/hufman/androidautoidrive/notifications/ReadoutControllerTest.kt @@ -0,0 +1,26 @@ +package me.hufman.androidautoidrive.notifications + +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import me.hufman.androidautoidrive.carapp.ReadoutController +import me.hufman.androidautoidrive.carapp.ReadoutState +import me.hufman.androidautoidrive.carapp.TTSState +import org.junit.Assert +import org.junit.Test +import org.mockito.kotlin.mock + +class ReadoutControllerTest { + @Test + fun testTTSCallback() { + val jsonObject = JsonParser.parseString("{\"TTSState\": {\"state\": 0, \"type\": \"app\", \"currentblock\": 0}}") as JsonObject + + val controller = ReadoutController("app", mock()) + controller.onTTSEvent(Gson().fromJson(jsonObject["TTSState"], TTSState::class.java)) + + Assert.assertEquals(ReadoutState.UNDEFINED, controller.currentState) + Assert.assertEquals("app", controller.currentName) + Assert.assertEquals(0, controller.currentBlock) + } + +} \ No newline at end of file