Skip to content

Commit

Permalink
Merge branch 'j29_as_bmw'
Browse files Browse the repository at this point in the history
  • Loading branch information
hufman committed Aug 5, 2024
2 parents 12de979 + d7a89ff commit 5f84029
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ class MainScreenshotTest {
@Test
fun j29Connection() {
whenever(mockScenario.connectionDebugging.isJ29Installed) doReturn true
whenever(mockScenario.carInfo.connectionBrand) doReturn "j29"
whenever(mockScenario.carInfo.capabilities) doReturn mapOf(
"hmi.type" to "J29 ID6L",
"hmi.version" to "NBTevo_ID5_2111",
Expand Down Expand Up @@ -326,4 +327,49 @@ class MainScreenshotTest {
))
screenshot("navigation_j29")
}

@Test
fun j29EntwicklerConnection() {
whenever(mockScenario.connectionDebugging.isJ29Installed) doReturn true
whenever(mockScenario.carInfo.connectionBrand) doReturn "bmw"
whenever(mockScenario.carInfo.capabilities) doReturn mapOf(
"hmi.type" to "J29 ID6L",
"hmi.version" to "NBTevo_ID5_2111",
"navi" to "true",
"tts" to "true",
"vehicle.type" to "J29"
)
whenever(mockScenario.navigationStatusModel.isConnected) doReturn true
whenever(mockScenario.navigationStatusModel.searchStatus) doReturnMutableContexted {""}

// real viewmodels need to be updated for the above mocked data
updateViewModels()

await().untilAsserted { onView(withId(R.id.drawer_layout))
.perform(DrawerActions.open())
.check(matches(isOpen())) }
onView(withId(R.id.nav_view)).perform(NavigationViewActions
.navigateTo(
R.id.nav_overview
))
screenshot("home_j29_entwickler")

await().untilAsserted { onView(withId(R.id.drawer_layout))
.perform(DrawerActions.open())
.check(matches(isOpen())) }
onView(withId(R.id.nav_view)).perform(NavigationViewActions
.navigateTo(
R.id.nav_connection
))
screenshot("connection_j29_entwickler")

await().untilAsserted { onView(withId(R.id.drawer_layout))
.perform(DrawerActions.open())
.check(matches(isOpen())) }
onView(withId(R.id.nav_view)).perform(NavigationViewActions
.navigateTo(
R.id.nav_navigation
))
screenshot("navigation_j29_entwickler")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class MockScenario(context: Context) {
on {carBrand} doReturn "BMW"
}
val carInfo = mock<CarInformation> {
on {connectionBrand} doReturn "bmw"
on {capabilities} doReturn mapOf(
"hmi.type" to "BMW ID5",
"hmi.version" to "NBTevo_ID5_1903",
Expand All @@ -51,6 +52,7 @@ class MockScenario(context: Context) {
}
val musicAppMode = mock<MusicAppMode> {
on {heuristicAudioContext()} doReturn true
on {shouldRequestAudioContext()} doReturn true
on {shouldId5Playback()} doReturn true
}
val carSummaryViewModel = CarSummaryModel(carInfo, MutableLiveData(false))
Expand Down Expand Up @@ -120,6 +122,8 @@ class MockScenario(context: Context) {
on {hasSmsPermission} doReturn true
on {hasLocationPermission} doReturn true
on {hasBackgroundPermission} doReturn true
on {hasBluetoothConnectPermission} doReturn true
on {hasPostNotificationsPermission} doReturn true
}

// https://stackoverflow.com/a/60119030/169035
Expand Down
34 changes: 25 additions & 9 deletions app/src/main/java/me/hufman/androidautoidrive/CarInformation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ open class CarInformation {
val listeners: Map<CarInformationObserver, Boolean> = _listeners

var isConnected = false
var connectionBrand: String? = null

var currentCapabilities: Map<String, String> = emptyMap()
var cachedCapabilities: Map<String, String> = emptyMap()
Expand Down Expand Up @@ -121,12 +122,17 @@ open class CarInformation {
open val isConnected: Boolean
get() = CarInformation.isConnected

open val connectionBrand: String?
get() = CarInformation.connectionBrand

open val capabilities: Map<String, String>
get() = if (currentCapabilities.isEmpty()) {
cachedCapabilities
} else {
currentCapabilities
}
val currentCapabilities: Map<String, String>
get() = CarInformation.currentCapabilities

open val cdsData: CDSData = CarInformation.cdsData
open val cachedCdsData: CDSData = CarInformation.cachedCdsData
Expand All @@ -142,11 +148,16 @@ open class CarInformationUpdater(val appSettings: MutableAppSettings): CarInform
onCarCapabilities()
}
}
override var connectionBrand: String?
get() = super.connectionBrand
set(value) {
CarInformation.connectionBrand = value
}

override var capabilities: Map<String, String>
get() = super.capabilities
set(value) {
currentCapabilities = value
CarInformation.currentCapabilities = value
cachedCapabilities = value.filterKeys { CACHED_CAPABILITY_KEYS.contains(it) }
onCarCapabilities()
}
Expand Down Expand Up @@ -192,25 +203,30 @@ class CarCapabilitiesSummarized(val carInformation: CarInformation) {
val isJ29: Boolean
get() = carInformation.capabilities["hmi.type"]?.startsWith("J29") == true

val isHmiSupported: Boolean
get() = carInformation.connectionBrand?.uppercase() == "BMW" || carInformation.connectionBrand?.uppercase() == "MINI"
val isHmiNotSupported: Boolean
get() = carInformation.connectionBrand?.uppercase() == "J29"

val isPopupSupported: Boolean
get() = !isJ29
get() = isHmiSupported
val isPopupNotSupported: Boolean
get() = isJ29
get() = isHmiNotSupported

val isTtsSupported: Boolean
get() = !isJ29 && carInformation.capabilities["tts"]?.lowercase(Locale.ROOT) == "true"
get() = isHmiSupported && carInformation.capabilities["tts"]?.lowercase(Locale.ROOT) == "true"
val isTtsNotSupported: Boolean
get() = isJ29 || carInformation.capabilities["tts"]?.lowercase(Locale.ROOT) == "false"
get() = isHmiNotSupported || carInformation.capabilities["tts"]?.lowercase(Locale.ROOT) == "false"

val isNaviSupported: Boolean
get() = !isJ29 && carInformation.capabilities["navi"]?.lowercase(Locale.ROOT) == "true"
get() = isHmiSupported && carInformation.capabilities["navi"]?.lowercase(Locale.ROOT) == "true"
val isNaviNotSupported: Boolean
get() = isJ29 || carInformation.capabilities["navi"]?.lowercase(Locale.ROOT) == "false"
get() = isHmiNotSupported || carInformation.capabilities["navi"]?.lowercase(Locale.ROOT) == "false"

val mapWidescreenSupported: Boolean
get() = !isJ29 && (carInformation.capabilities["hmi.display-width"]?.toIntOrNull() ?: 0) >= 1000
get() = isHmiSupported && (carInformation.capabilities["hmi.display-width"]?.toIntOrNull() ?: 0) >= 1000
val mapWidescreenUnsupported: Boolean
get() = isJ29 || (carInformation.capabilities["hmi.display-width"]?.toIntOrNull() ?: 9999) < 1000
get() = isHmiNotSupported || (carInformation.capabilities["hmi.display-width"]?.toIntOrNull() ?: 9999) < 1000
val mapWidescreenCrashes: Boolean
get() = carInformation.capabilities["hmi.version"]?.lowercase(Locale.ROOT)?.startsWith("entryevo_") == true
val isF20orF21: Boolean
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/me/hufman/androidautoidrive/CarProber.kt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ class CarProber(val securityAccess: SecurityAccess, val settings: AppSettings, v
success = true
break
}
if (hmiType?.startsWith("J29") == true && (brand == "bmw" || brand == "mini")) {
// the car has ENTWICKLER_MENUE active and ignores the brand restriction on the cert
setConnectedState(host, port, brand)
success = true
break
}
if (brand == "j29") {
setConnectedState(host, port, "j29")
success = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ class MainService: Service() {
scheduleShutdownTimeout()
handler.post(btfetchUuidsWithSdp)
}
carInformationUpdater.connectionBrand = iDriveConnectionReceiver.brand
carInformationUpdater.isConnected = iDriveConnectionReceiver.isConnected && securityAccess.isConnected()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ class CarCapabilitiesViewModel(val carInformation: CarInformation, val musicAppM
val summarized = CarCapabilitiesSummarized(carInformation)

// only these brands of cars support RHMI apps
val carBrandSupported = summarized.isBmw || summarized.isMini
// check the cert brand, in case of friendly J29s
val carBrandSupported = carInformation.connectionBrand?.uppercase() == "BMW" || carInformation.connectionBrand?.uppercase() == "MINI"
_isCarConnected.value = carInformation.capabilities.isNotEmpty() && carBrandSupported
_isJ29Connected.value = summarized.isJ29

_isAudioContextSupported.value = !summarized.isJ29 && musicAppMode.heuristicAudioContext()
_isAudioContextSupported.value = carBrandSupported && musicAppMode.heuristicAudioContext()
if (carBrandSupported && musicAppMode.heuristicAudioContext()) {
_audioContextStatus.value = { getString(R.string.txt_capabilities_audiocontext_yes) }
_audioContextHint.value = { "" }
Expand All @@ -98,7 +99,7 @@ class CarCapabilitiesViewModel(val carInformation: CarInformation, val musicAppM
}
}

_isAudioStateSupported.value = !summarized.isJ29 && musicAppMode.supportsId5Playback()
_isAudioStateSupported.value = carBrandSupported && musicAppMode.supportsId5Playback()
if (carBrandSupported && musicAppMode.supportsId5Playback()) {
_audioStateStatus.value = { getString(R.string.txt_capabilities_audiostate_yes) }
_audioStateHint.value = { "" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class CarSummaryModel(carInfoOverride: CarInformation? = null, val showAdvancedS
when (brand) {
"BMW" -> _carLogo.value = { ContextCompat.getDrawable(this, R.drawable.logo_bmw) }
"MINI" -> _carLogo.value = { ContextCompat.getDrawable(this, R.drawable.logo_mini) }
"J29" -> _carLogo.value = { ContextCompat.getDrawable(this, R.drawable.logo_toyota) }
else -> _carLogo.value = { null }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,17 @@ class ConnectionStatusModel(val connection: CarConnectionDebugging, val carInfo:
}

// current car overview
val brand = if (connection.isBCLConnected) connection.carBrand?.uppercase(Locale.ROOT) else null
val brand = if (connection.isBCLConnected) {
// hmi.type will say the actual car brand, after loading
// but connection carBrand is just based on the auth cert
carInfo.currentCapabilities["hmi.type"]?.split(' ')?.first() ?:
connection.carBrand?.uppercase(Locale.ROOT)
} else null
_carBrand.value = brand
when (brand) {
"BMW" -> _carLogo.value = { ContextCompat.getDrawable(this, R.drawable.logo_bmw) }
"MINI" -> _carLogo.value = { ContextCompat.getDrawable(this, R.drawable.logo_mini) }
"J29" -> _carLogo.value = { ContextCompat.getDrawable(this, R.drawable.logo_toyota) }
else -> _carLogo.value = { null }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ class NavigationStatusModel(val carInformation: CarInformation,

fun update() {
// only these brands of cars support the RHMI app necessary to trigger car nav
val carBrandSupported = carInformation.capabilities["hmi.type"]?.startsWith("BMW") == true ||
carInformation.capabilities["hmi.type"]?.startsWith("MINI") == true
val carBrandSupported = carInformation.connectionBrand?.uppercase() == "BMW" || carInformation.connectionBrand?.uppercase() == "MINI"
_isConnected.value = carInformation.isConnected && carBrandSupported

val capabilities = carInformation.capabilities
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/res/drawable/logo_toyota.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="6.35"
android:viewportHeight="6.35">
<path
android:pathData="M 5.9557567,3.0417812 A 2.7350471,1.8915279 0 0 1 3.2207096,4.9333091 2.7350471,1.8915279 0 0 1 0.48566246,3.0417812 2.7350471,1.8915279 0 0 1 3.2207096,1.1502533 2.7350471,1.8915279 0 0 1 5.9557567,3.0417812 Z"
android:strokeWidth="0.264583"
android:strokeColor="#b8cad1"/>
<path
android:pathData="M 5.2911658,2.0386927 A 2.0576756,0.83421594 0 0 1 3.2334902,2.8729087 2.0576756,0.83421594 0 0 1 1.1758146,2.0386927 2.0576756,0.83421594 0 0 1 3.2334902,1.2044768 2.0576756,0.83421594 0 0 1 5.2911658,2.0386927 Z"
android:strokeWidth="0.264583"
android:strokeColor="#b8cad1"/>
<path
android:pathData="M 3.8759973,3.0545619 A 0.59138465,1.6998188 0 0 1 3.2846127,4.7543807 0.59138465,1.6998188 0 0 1 2.693228,3.0545619 0.59138465,1.6998188 0 0 1 3.2846127,1.354743 0.59138465,1.6998188 0 0 1 3.8759973,3.0545619 Z"
android:strokeWidth="0.264583"
android:strokeColor="#b8cad1"/>
</vector>
6 changes: 5 additions & 1 deletion app/src/main/res/layout/fragment_car_capabilities.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,15 @@
<LinearLayout
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="@{viewModel.isJ29Connected()}">
android:visibility="@{viewModel.isJ29Connected() &amp;&amp; !viewModel.isCarConnected()}">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_setup_toyota_hint" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_setup_toyota_hint2" />
</LinearLayout>
</LinearLayout>
</layout>
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@
<string name="txt_setup_bmw_ready">The app is ready to connect to a BMW</string>
<string name="txt_setup_mini_ready">The app is ready to connect to a Mini</string>
<string name="txt_setup_toyota_ready">The app is ready to connect to a Toyota Supra</string>
<string name="txt_setup_toyota_hint">Toyota support is very limited, currently only providing car data</string>
<string name="txt_setup_toyota_hint">Toyota support is very limited, only providing car data by default</string>
<string name="txt_setup_toyota_hint2">To enable full support in the Toyota Supra, use BimmerCode to code ENTWICKLER_MENUE to aktiv</string>
<string name="txt_setup_bmw_missing">Not ready to connect to a BMW:</string>
<string name="txt_setup_mini_missing">Not ready to connect to a Mini:</string>
<string name="txt_setup_bmw_connected_65">BMW Connected 6.5 is installed, which sometimes doesn\'t start the car connection. Try downgrading to 6.4 or upgrading to MyBMW if you encounter problems</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class CarCapabilitiesModelTest {
@Test
fun testNoCapabilities() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf()
}
val musicAppMode = mock<MusicAppMode> {
Expand All @@ -47,6 +48,7 @@ class CarCapabilitiesModelTest {
@Test
fun testId4Usb() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf("hmi.type" to "MINI ID4", "tts" to "true", "navi" to "false")
}
val musicAppMode = mock<MusicAppMode> {
Expand Down Expand Up @@ -91,6 +93,7 @@ class CarCapabilitiesModelTest {
@Test
fun testId4UsbContext() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf("hmi.type" to "MINI ID4", "tts" to "true", "navi" to "false")
}
val musicAppMode = mock<MusicAppMode> {
Expand Down Expand Up @@ -123,6 +126,7 @@ class CarCapabilitiesModelTest {
@Test
fun testId5() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf("hmi.type" to "MINI ID5", "tts" to "false", "navi" to "true")
}
val musicAppMode = mock<MusicAppMode> {
Expand Down Expand Up @@ -167,6 +171,7 @@ class CarCapabilitiesModelTest {
@Test
fun testId5NoContext() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf("hmi.type" to "MINI ID5", "tts" to "false", "navi" to "true")
}
val musicAppMode = mock<MusicAppMode> {
Expand Down Expand Up @@ -203,6 +208,7 @@ class CarCapabilitiesModelTest {
@Test
fun testId5Spotify() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf("hmi.type" to "MINI ID5", "tts" to "false", "navi" to "true")
}
val musicAppMode = mock<MusicAppMode> {
Expand Down Expand Up @@ -239,6 +245,7 @@ class CarCapabilitiesModelTest {
@Test
fun testId5Classic() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "mini"
on { capabilities } doReturn mapOf("hmi.type" to "MINI ID5", "tts" to "false", "navi" to "true")
}
val musicAppMode = mock<MusicAppMode> {
Expand Down Expand Up @@ -266,7 +273,8 @@ class CarCapabilitiesModelTest {
@Test
fun testJ29() {
val carInfo = mock<CarInformation> {
on { capabilities } doReturn mapOf("hmi.type" to "J29 ID6L")
on { connectionBrand } doReturn "j29"
on { capabilities } doReturn mapOf("hmi.type" to "J29 ID6L", "tts" to "true", "navi" to "false")
}
val musicAppMode = mock<MusicAppMode> {
on { heuristicAudioContext() } doReturn true
Expand All @@ -289,4 +297,32 @@ class CarCapabilitiesModelTest {
assertEquals(true, viewModel.isNaviNotSupported.value)

}

@Test
fun testJ29_entwickler() {
val carInfo = mock<CarInformation> {
on { connectionBrand } doReturn "bmw"
on { capabilities } doReturn mapOf("hmi.type" to "J29 ID6L", "tts" to "true", "navi" to "false")
}
val musicAppMode = mock<MusicAppMode> {
on { heuristicAudioContext() } doReturn true
on { shouldRequestAudioContext() } doReturn true
on { isId4() } doReturn false
on { supportsId5Playback() } doReturn true
on { shouldId5Playback() } doReturn false
on { isNewSpotifyInstalled() } doReturn true
}
val viewModel = CarCapabilitiesViewModel(carInfo, musicAppMode).apply { update() }
assertEquals(true, viewModel.isCarConnected.value)
assertEquals(true, viewModel.isJ29Connected.value)
assertEquals(true, viewModel.isAudioContextSupported.value)
assertEquals(true, viewModel.isAudioStateSupported.value)
assertEquals(true, viewModel.isPopupSupported.value)
assertEquals(false, viewModel.isPopupNotSupported.value)
assertEquals(true, viewModel.isTtsSupported.value)
assertEquals(false, viewModel.isTtsNotSupported.value)
assertEquals(false, viewModel.isNaviSupported.value)
assertEquals(true, viewModel.isNaviNotSupported.value)

}
}
Loading

0 comments on commit 5f84029

Please sign in to comment.