Integrate current PR marker controls while preserving the DiLink 5.1 stream
This commit is contained in:
commit
9edcf527ce
7 files changed
+180
-76
No files matched your search
@@ -53,8 +53,9 @@ object AirPlayPersistence {
|
||||
private const val KEY_FPS = "display_fps"
|
||||
private const val KEY_MEDIA_BUFFER_MS = "media_buffer_ms"
|
||||
private const val KEY_CLUSTER_MAP = "cluster_map_enabled"
|
||||
private const val KEY_CLUSTER_MAP_LIFT = "cluster_map_lift_percent"
|
||||
private const val KEY_CLUSTER_MAP_SHIFT = "cluster_map_shift_left_percent"
|
||||
private const val KEY_CLUSTER_MAP_SCALE = "cluster_map_scale_percent"
|
||||
private const val KEY_CLUSTER_MARKER_X = "cluster_marker_horizontal_step"
|
||||
private const val KEY_CLUSTER_MARKER_Y = "cluster_marker_vertical_step"
|
||||
private const val KEY_WIDTH_PHYSICAL_MM = "display_width_physical_mm"
|
||||
private const val KEY_PHYSICAL_SIZE_BASIS = "display_physical_size_basis"
|
||||
private const val KEY_MAX_DETECTED_WIDTH = "display_max_detected_width"
|
||||
@@ -410,24 +411,32 @@ object AirPlayPersistence {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putBoolean(KEY_CLUSTER_MAP, enabled).apply()
|
||||
}
|
||||
|
||||
fun loadClusterMapLiftPercent(context: Context): Int = CarPlayClusterDisplay.DEFAULT_LIFT_PERCENT.let { default ->
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getInt(KEY_CLUSTER_MAP_LIFT, default)
|
||||
.takeIf { it in CarPlayClusterDisplay.liftPresets } ?: default
|
||||
fun loadClusterMapScalePercent(context: Context): Int = CarPlayClusterDisplay.STREAM_SCALE_PERCENT.let { default ->
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getInt(KEY_CLUSTER_MAP_SCALE, default)
|
||||
.takeIf { it in CarPlayClusterDisplay.scalePresets } ?: default
|
||||
}
|
||||
|
||||
fun saveClusterMapLiftPercent(context: Context, percent: Int) {
|
||||
if (percent !in CarPlayClusterDisplay.liftPresets) return
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putInt(KEY_CLUSTER_MAP_LIFT, percent).apply()
|
||||
fun saveClusterMapScalePercent(context: Context, percent: Int) {
|
||||
if (percent !in CarPlayClusterDisplay.scalePresets) return
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putInt(KEY_CLUSTER_MAP_SCALE, percent).apply()
|
||||
}
|
||||
|
||||
fun loadClusterMapShiftPercent(context: Context): Int = CarPlayClusterDisplay.DEFAULT_SHIFT_PERCENT.let { default ->
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getInt(KEY_CLUSTER_MAP_SHIFT, default)
|
||||
.takeIf { it in CarPlayClusterDisplay.shiftPresets } ?: default
|
||||
fun loadClusterMarkerHorizontalStep(context: Context): Int =
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getInt(KEY_CLUSTER_MARKER_X, 0)
|
||||
.coerceIn(CarPlayClusterDisplay.horizontalSteps)
|
||||
|
||||
fun saveClusterMarkerHorizontalStep(context: Context, step: Int) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putInt(KEY_CLUSTER_MARKER_X, step.coerceIn(CarPlayClusterDisplay.horizontalSteps)).apply()
|
||||
}
|
||||
|
||||
fun saveClusterMapShiftPercent(context: Context, percent: Int) {
|
||||
if (percent !in CarPlayClusterDisplay.shiftPresets) return
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putInt(KEY_CLUSTER_MAP_SHIFT, percent).apply()
|
||||
fun loadClusterMarkerVerticalStep(context: Context): Int =
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getInt(KEY_CLUSTER_MARKER_Y, 0)
|
||||
.coerceIn(CarPlayClusterDisplay.verticalSteps)
|
||||
|
||||
fun saveClusterMarkerVerticalStep(context: Context, step: Int) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putInt(KEY_CLUSTER_MARKER_Y, step.coerceIn(CarPlayClusterDisplay.verticalSteps)).apply()
|
||||
}
|
||||
|
||||
fun loadRightHandDrive(context: Context): Boolean =
|
||||
|
||||
@@ -687,9 +687,12 @@ class CarPlayHostActivity : ComponentActivity() {
|
||||
return CarPlayClusterDisplay.config(
|
||||
size.x,
|
||||
size.y,
|
||||
AirPlayPersistence.loadClusterMapLiftPercent(this),
|
||||
AirPlayPersistence.loadClusterMapShiftPercent(this),
|
||||
).also { appendLog("Cluster map: requesting ${size.x}x${size.y} cluster screen safeArea=${it.safeArea}") }
|
||||
AirPlayPersistence.loadClusterMapScalePercent(this),
|
||||
AirPlayPersistence.loadClusterMarkerHorizontalStep(this),
|
||||
AirPlayPersistence.loadClusterMarkerVerticalStep(this),
|
||||
).also {
|
||||
appendLog("Cluster map: requesting ${it.widthPixels}x${it.heightPixels} on ${size.x}x${size.y} safeArea=${it.safeArea}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
|
||||
@@ -27,7 +27,11 @@ internal object DiLink51ClusterLayout {
|
||||
}
|
||||
const val STREAM_WIDTH = 1920
|
||||
const val STREAM_HEIGHT = 720
|
||||
fun streamConfig(): AirPlayDisplayConfig = CarPlayClusterDisplay.config(STREAM_WIDTH, STREAM_HEIGHT, 0, 0)
|
||||
// DiLink 5.1 retains its measured native-size stream and bottom-centered crop.
|
||||
// DiLink 5's marker safe area and scale controls must not alter this profile.
|
||||
fun streamConfig(): AirPlayDisplayConfig = CarPlayClusterDisplay.config(
|
||||
STREAM_WIDTH, STREAM_HEIGHT, scalePercent = 100,
|
||||
).copy(safeArea = null)
|
||||
|
||||
fun supported(fingerprint: String = Build.FINGERPRINT): Boolean = fingerprint == FINGERPRINT
|
||||
fun theme(context: Context): Theme = Theme.entries.firstOrNull {
|
||||
|
||||
@@ -234,7 +234,7 @@ class DiPlayActivity : ComponentActivity() {
|
||||
"Shows the iPhone's cluster map on the instrument cluster. Choose Small or Full screen navi in the cluster's steering-wheel menu.",
|
||||
AirPlayPersistence.loadClusterMapEnabled(this)) {
|
||||
AirPlayPersistence.saveClusterMapEnabled(this, it)
|
||||
reconnectForClusterMap(force = true)
|
||||
reconnectForClusterMap()
|
||||
}
|
||||
if (DiLink51ClusterLayout.supported()) {
|
||||
val automatic = DiLink51ClusterLayout.automatic(this)
|
||||
@@ -270,19 +270,27 @@ class DiPlayActivity : ComponentActivity() {
|
||||
reconnectForClusterMap()
|
||||
}
|
||||
} else {
|
||||
val lifts = CarPlayClusterDisplay.liftPresets
|
||||
choice(card, "Car position on cluster map", listOf("Bottom", "Higher · default", "Much higher"),
|
||||
lifts.indexOf(AirPlayPersistence.loadClusterMapLiftPercent(this)).coerceAtLeast(0)) {
|
||||
AirPlayPersistence.saveClusterMapLiftPercent(this, lifts[it])
|
||||
reconnectForClusterMap()
|
||||
val sizes = CarPlayClusterDisplay.scalePresets
|
||||
choice(card, "Cluster map size", listOf("Standard · sharpest", "Larger · default", "Largest"),
|
||||
sizes.indexOf(AirPlayPersistence.loadClusterMapScalePercent(this)).coerceAtLeast(0)) {
|
||||
AirPlayPersistence.saveClusterMapScalePercent(this, sizes[it])
|
||||
}
|
||||
val shifts = CarPlayClusterDisplay.shiftPresets
|
||||
choice(card, "Car position across cluster map",
|
||||
listOf("Center", "Left · by 1/12", "Left · by 1/10", "Left · by 1/9 · default", "Left · by 1/8", "Left · by 1/6", "Far left · by 1/3"),
|
||||
shifts.indexOf(AirPlayPersistence.loadClusterMapShiftPercent(this)).coerceAtLeast(0)) {
|
||||
AirPlayPersistence.saveClusterMapShiftPercent(this, shifts[it])
|
||||
reconnectForClusterMap()
|
||||
val across = CarPlayClusterDisplay.horizontalSteps.toList()
|
||||
choice(card, "Car marker · horizontal", across.map { markerStepLabel(it, "Left", "Right") },
|
||||
across.indexOf(AirPlayPersistence.loadClusterMarkerHorizontalStep(this)).coerceAtLeast(0)) {
|
||||
AirPlayPersistence.saveClusterMarkerHorizontalStep(this, across[it])
|
||||
}
|
||||
val upDown = CarPlayClusterDisplay.verticalSteps.toList()
|
||||
choice(card, "Car marker · vertical", upDown.map { markerStepLabel(it, "Up", "Down") },
|
||||
upDown.indexOf(AirPlayPersistence.loadClusterMarkerVerticalStep(this)).coerceAtLeast(0)) {
|
||||
AirPlayPersistence.saveClusterMarkerVerticalStep(this, upDown[it])
|
||||
}
|
||||
card.addView(button("Reset car marker to centre", false) {
|
||||
AirPlayPersistence.saveClusterMarkerHorizontalStep(this, 0)
|
||||
AirPlayPersistence.saveClusterMarkerVerticalStep(this, 0)
|
||||
render()
|
||||
reconnectForClusterMap()
|
||||
}, matchButton(10, 56))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,9 +445,17 @@ class DiPlayActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// The cluster screen is described at connection time, so a running session reconnects to apply.
|
||||
private fun reconnectForClusterMap(force: Boolean = false) {
|
||||
if ((force || AirPlayPersistence.loadClusterMapEnabled(this)) && CarPlayBackgroundSession.hasSession()) connect(AirPlayPersistence.loadWirelessEnabled(this))
|
||||
// "Left 20 %", "Centre · default", "Down 10 %": a signed step reads as a direction and a distance.
|
||||
private fun markerStepLabel(step: Int, negative: String, positive: String): String = when {
|
||||
step == 0 -> "Centre · default"
|
||||
step < 0 -> "$negative ${-step * CarPlayClusterDisplay.MARKER_STEP_PERCENT} %"
|
||||
else -> "$positive ${step * CarPlayClusterDisplay.MARKER_STEP_PERCENT} %"
|
||||
}
|
||||
|
||||
// The cluster screen is described at connection time, so a running session reconnects over
|
||||
// its current link. The position choices need no call: "Apply and reconnect" already does it.
|
||||
private fun reconnectForClusterMap() {
|
||||
if (CarPlayBackgroundSession.hasSession()) connect(AirPlayPersistence.loadWirelessEnabled(this))
|
||||
}
|
||||
|
||||
private fun applyWirelessLink(mode: WirelessHotspotMode) {
|
||||
|
||||
@@ -21,7 +21,8 @@ Validated on DiLink5.0 / Android12, firmware `BYD-AUTO/DiLink5.0/DiLink5.0:12/SK
|
||||
- Turn on "CarPlay map on instrument cluster" under BYD navigation. The switch appears only when a cluster projection display is visible to the app.
|
||||
- In the cluster's own steering-wheel menu, choose "Full screen navi" or "Small screen navi". "Turn on by navi" shows the map only during stock BYD navigation, so it keeps showing the arrow output instead.
|
||||
- The cluster's speed readout stays visible. "Small screen navi" crops the same picture on the cluster side; Android does not report that crop.
|
||||
- "Car position on cluster map" and "Car position across cluster map" set CarPlay's safe area, which moves the car marker. The defaults, Higher and 1/9 to the left, fit the tested cluster.
|
||||
- The car marker is placed through CarPlay's safe area: the centre of the panel (x 35–64 %, y 16–75 %), measured with a calibration grid to be clear of BYD's own readouts in both Full and Small screen navi. "Car marker · horizontal" (Left 40 % … Right 40 %) and "Car marker · vertical" (Up 30 % … Down 30 %) move it from there in 10 % steps of the panel. Near a panel edge the safe area shrinks so the marker stays at its centre.
|
||||
- "Cluster map size" sets the stream size, which the cluster scales up to the panel: Standard (100 %, sharpest), Larger (83 %, 1600x600, default) or Largest (67 %). Apple Maps ignores the reported physical size on the cluster, so resolution is the only way to change the map's scale.
|
||||
|
||||
How it works: the iPhone lists the cluster content it offers in its `/info` request (`altScreenURLs`). DiPlay declares a second display of the cluster's size with no input devices and `initialURL=maps:/car/instrumentcluster/map`; without an initial URL the iPhone streams only a black frame. BYD exposes the cluster projection area as public presentation displays owned by `com.byd.containerservice`. The stock map's display (`fission_bg_XDJAScreenProjection`) is hidden from third-party apps, but its `shared_…_0` sibling is composited on top of it, so DiPlay shows a `Presentation` there.
|
||||
|
||||
|
||||
@@ -5,37 +5,86 @@ package com.shilapi.xcertplay.airplay
|
||||
*
|
||||
* The iPhone lists the cluster content it offers in its /info request (`altScreenURLs`); without
|
||||
* an initial URL it streams a black frame. The cluster has no input. Apple Maps keeps the car
|
||||
* position inside the safe area while the map still fills the panel, so bottom and right insets
|
||||
* move the car up and to the left, away from the cluster's own readouts.
|
||||
* position inside the safe area while the map still fills the panel, so the safe area is set to
|
||||
* the part of the panel the cluster leaves uncovered.
|
||||
*/
|
||||
object CarPlayClusterDisplay {
|
||||
const val MAP_URL = "maps:/car/instrumentcluster/map"
|
||||
|
||||
/** Bottom inset in percent of the height. The default suits the BYD cluster. */
|
||||
const val DEFAULT_LIFT_PERCENT = 25
|
||||
val liftPresets = listOf(0, DEFAULT_LIFT_PERCENT, 45)
|
||||
/**
|
||||
* Where the car marker goes, as percent of the panel (left, top, right, bottom). Measured on a
|
||||
* DiLink 5.0 cluster with a calibration grid. In "Full screen navi" BYD draws a status row,
|
||||
* turn/ADAS icons, the ADAS lane view (from x 68 %), speed and power readouts and a bottom band
|
||||
* over the map; "Small screen navi" crops the panel to a centre window of about x 31–68 %,
|
||||
* y 16–89 %. This centre area is clear in both modes, so no per-mode setting is needed.
|
||||
*/
|
||||
val SAFE_AREA_PERCENT = AirPlayInsets(top = 16, bottom = 25, left = 35, right = 36)
|
||||
|
||||
/** Right inset in percent of the width; the car moves left by half of it (22 % ≈ 1/9). */
|
||||
const val DEFAULT_SHIFT_PERCENT = 22
|
||||
val shiftPresets = listOf(0, 17, 20, DEFAULT_SHIFT_PERCENT, 25, 33, 67)
|
||||
/** The driver can move the marker from that centre, in steps of 10 % of the panel. */
|
||||
const val MARKER_STEP_PERCENT = 10
|
||||
val horizontalSteps = -4..4 // negative = left
|
||||
val verticalSteps = -3..3 // negative = up
|
||||
|
||||
private const val WIDTH_PHYSICAL_MM = 292 // a 12.3-inch 8:3 cluster panel
|
||||
/** Where the car marker lands, in percent of the panel (x from the left, y from the top). */
|
||||
fun markerPercent(horizontalStep: Int, verticalStep: Int): Pair<Double, Double> {
|
||||
val area = SAFE_AREA_PERCENT
|
||||
val x = (area.left + 100 - area.right) / 2.0 + horizontalStep.coerceIn(horizontalSteps) * MARKER_STEP_PERCENT
|
||||
val y = (area.top + 100 - area.bottom) / 2.0 + verticalStep.coerceIn(verticalSteps) * MARKER_STEP_PERCENT
|
||||
return x.coerceIn(MARKER_MARGIN, 100 - MARKER_MARGIN) to y.coerceIn(MARKER_MARGIN, 100 - MARKER_MARGIN)
|
||||
}
|
||||
|
||||
private const val MARKER_MARGIN = 5.0
|
||||
|
||||
/**
|
||||
* Stream size in percent of the panel. The cluster scales the stream up, so a smaller stream
|
||||
* gives a larger map: 83 % is clearly larger and still sharp; 50 % was visibly blurry.
|
||||
*/
|
||||
const val STREAM_SCALE_PERCENT = 83
|
||||
val scalePresets = listOf(100, STREAM_SCALE_PERCENT, 67)
|
||||
|
||||
private const val WIDTH_PHYSICAL_MM = 292 // a 12.3-inch 8:3 cluster panel; Apple Maps ignores it here
|
||||
private const val FPS = 30
|
||||
|
||||
fun config(widthPixels: Int, heightPixels: Int, liftPercent: Int, shiftPercent: Int): AirPlayDisplayConfig {
|
||||
val lift = heightPixels * liftPercent.coerceIn(0, 90) / 100
|
||||
val shift = widthPixels * shiftPercent.coerceIn(0, 90) / 100
|
||||
fun config(
|
||||
widthPixels: Int,
|
||||
heightPixels: Int,
|
||||
scalePercent: Int = STREAM_SCALE_PERCENT,
|
||||
horizontalStep: Int = 0,
|
||||
verticalStep: Int = 0,
|
||||
): AirPlayDisplayConfig {
|
||||
// Height rounds to a multiple of 8 and width follows it, so the panel's aspect is kept
|
||||
// (83 % of 1920x720 gives exactly 1600x600). The cluster scales the stream to the panel.
|
||||
val scale = scalePercent.coerceIn(25, 100)
|
||||
val height = roundTo8(heightPixels * scale / 100.0)
|
||||
val width = roundTo8(height * widthPixels.toDouble() / heightPixels)
|
||||
return AirPlayDisplayConfig(
|
||||
widthPixels = widthPixels,
|
||||
heightPixels = heightPixels,
|
||||
widthPixels = width,
|
||||
heightPixels = height,
|
||||
widthPhysicalMm = WIDTH_PHYSICAL_MM,
|
||||
heightPhysicalMm = Math.round(WIDTH_PHYSICAL_MM * heightPixels.toDouble() / widthPixels).toInt(),
|
||||
heightPhysicalMm = Math.round(WIDTH_PHYSICAL_MM * height.toDouble() / width).toInt(),
|
||||
fps = FPS,
|
||||
primaryInputDevice = 0,
|
||||
features = 0,
|
||||
initialUrl = MAP_URL,
|
||||
safeArea = if (lift > 0 || shift > 0) AirPlayInsets(bottom = lift, right = shift) else null,
|
||||
safeArea = safeArea(width, height, horizontalStep, verticalStep),
|
||||
safeAreaDrawOutside = true,
|
||||
)
|
||||
}
|
||||
|
||||
// The safe area keeps its measured size around the marker and shrinks only where the marker
|
||||
// comes close to a panel edge, so the marker always sits at its centre.
|
||||
private fun safeArea(width: Int, height: Int, horizontalStep: Int, verticalStep: Int): AirPlayInsets {
|
||||
val area = SAFE_AREA_PERCENT
|
||||
val (x, y) = markerPercent(horizontalStep, verticalStep)
|
||||
val halfWidth = minOf((100 - area.left - area.right) / 2.0, x, 100 - x)
|
||||
val halfHeight = minOf((100 - area.top - area.bottom) / 2.0, y, 100 - y)
|
||||
return AirPlayInsets(
|
||||
top = (height * (y - halfHeight) / 100).toInt(),
|
||||
bottom = (height * (100 - y - halfHeight) / 100).toInt(),
|
||||
left = (width * (x - halfWidth) / 100).toInt(),
|
||||
right = (width * (100 - x - halfWidth) / 100).toInt(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun roundTo8(value: Double): Int = (Math.round(value / 8) * 8).toInt().coerceAtLeast(8)
|
||||
}
|
||||
@@ -1,19 +1,12 @@
|
||||
package com.shilapi.xcertplay.airplay
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class CarPlayClusterDisplayTest {
|
||||
@Test
|
||||
fun defaultClusterIsAdvertisedAsAnInputlessMapScreen() {
|
||||
val cluster = CarPlayClusterDisplay.config(
|
||||
1920,
|
||||
720,
|
||||
CarPlayClusterDisplay.DEFAULT_LIFT_PERCENT,
|
||||
CarPlayClusterDisplay.DEFAULT_SHIFT_PERCENT,
|
||||
)
|
||||
val info = AirPlayInfoPlist.build(
|
||||
AirPlayConfig(
|
||||
deviceName = "test",
|
||||
@@ -21,7 +14,7 @@ class CarPlayClusterDisplayTest {
|
||||
btMac = "02:00:00:00:00:02",
|
||||
sourceVersion = "366.0",
|
||||
main = AirPlayDisplayConfig(widthPixels = 1280, heightPixels = 720),
|
||||
cluster = cluster,
|
||||
cluster = CarPlayClusterDisplay.config(1920, 720),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -29,8 +22,9 @@ class CarPlayClusterDisplayTest {
|
||||
assertEquals(2, displays.size)
|
||||
val alt = displays[1] as Map<*, *>
|
||||
assertEquals(111, alt["type"])
|
||||
assertEquals(1920, alt["widthPixels"])
|
||||
assertEquals(720, alt["heightPixels"])
|
||||
// 83 % of the 1920x720 panel, same 8:3 aspect.
|
||||
assertEquals(1600, alt["widthPixels"])
|
||||
assertEquals(600, alt["heightPixels"])
|
||||
assertEquals(0, alt["features"])
|
||||
assertEquals(0, alt["primaryInputDevice"])
|
||||
assertEquals("maps:/car/instrumentcluster/map", alt["initialURL"])
|
||||
@@ -38,14 +32,14 @@ class CarPlayClusterDisplayTest {
|
||||
assertEquals(110, alt["heightPhysical"])
|
||||
|
||||
val view = (alt["viewAreas"] as List<*>).single() as Map<*, *>
|
||||
assertEquals(1920, view["widthPixels"])
|
||||
assertEquals(720, view["heightPixels"])
|
||||
assertEquals(1600, view["widthPixels"])
|
||||
assertEquals(600, view["heightPixels"])
|
||||
val safe = view["safeArea"] as Map<*, *>
|
||||
// 25 % off the bottom and 22 % off the right: the car sits higher and about 1/9 further left.
|
||||
assertEquals(1498, safe["widthPixels"])
|
||||
assertEquals(540, safe["heightPixels"])
|
||||
assertEquals(0, safe["originXPixels"])
|
||||
assertEquals(0, safe["originYPixels"])
|
||||
// The centre area clear in both cluster navi modes: left 35 %, right 36 %, top 16 %, bottom 25 %.
|
||||
assertEquals(560, safe["originXPixels"])
|
||||
assertEquals(96, safe["originYPixels"])
|
||||
assertEquals(464, safe["widthPixels"])
|
||||
assertEquals(354, safe["heightPixels"])
|
||||
assertEquals(true, safe["drawUIOutsideSafeArea"])
|
||||
|
||||
// The main screen keeps its touch and knob features.
|
||||
@@ -53,22 +47,50 @@ class CarPlayClusterDisplayTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noOffsetsLeaveTheWholeClusterAsTheSafeArea() {
|
||||
val cluster = CarPlayClusterDisplay.config(1920, 720, 0, 0)
|
||||
fun scaledStreamsKeepThePanelAspect() {
|
||||
val sizes = (CarPlayClusterDisplay.scalePresets + 50).map {
|
||||
CarPlayClusterDisplay.config(1920, 720, scalePercent = it).let { c -> c.widthPixels to c.heightPixels }
|
||||
}
|
||||
|
||||
assertNull(cluster.safeArea)
|
||||
assertEquals(listOf(1920 to 720, 1600 to 600, 1280 to 480, 960 to 360), sizes)
|
||||
assertTrue(CarPlayClusterDisplay.STREAM_SCALE_PERCENT in CarPlayClusterDisplay.scalePresets)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun offsetsAreBoundedSoTheSafeAreaNeverCollapses() {
|
||||
val cluster = CarPlayClusterDisplay.config(1000, 400, 150, -20)
|
||||
fun markerStepsMoveTheWholeSafeArea() {
|
||||
val centre = CarPlayClusterDisplay.config(1920, 720, scalePercent = 100).safeArea!!
|
||||
val farLeftHigher = CarPlayClusterDisplay.config(1920, 720, scalePercent = 100, horizontalStep = -2, verticalStep = -1).safeArea!!
|
||||
|
||||
assertEquals(AirPlayInsets(bottom = 360, right = 0), cluster.safeArea)
|
||||
// Two 10 % steps left (384 px) and one 10 % step up (72 px), same size as the centred area.
|
||||
assertEquals(centre.left - 384, farLeftHigher.left)
|
||||
assertEquals(centre.right + 384, farLeftHigher.right)
|
||||
assertEquals(centre.top - 72, farLeftHigher.top)
|
||||
assertEquals(centre.bottom + 72, farLeftHigher.bottom)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun defaultsAreAmongTheOfferedChoices() {
|
||||
assertTrue(CarPlayClusterDisplay.DEFAULT_LIFT_PERCENT in CarPlayClusterDisplay.liftPresets)
|
||||
assertTrue(CarPlayClusterDisplay.DEFAULT_SHIFT_PERCENT in CarPlayClusterDisplay.shiftPresets)
|
||||
fun markerStepsAreBounded() {
|
||||
val beyond = CarPlayClusterDisplay.config(1920, 720, scalePercent = 100, horizontalStep = 9, verticalStep = -9).safeArea
|
||||
val limit = CarPlayClusterDisplay.config(1920, 720, scalePercent = 100, horizontalStep = 4, verticalStep = -3).safeArea
|
||||
|
||||
assertEquals(limit, beyond)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nearAnEdgeTheSafeAreaShrinksAroundTheMarker() {
|
||||
val top = CarPlayClusterDisplay.config(1000, 1000, scalePercent = 100, verticalStep = -3).safeArea!!
|
||||
|
||||
// Marker at 15.5 % from the top: the area reaches the top edge and stays centred on it.
|
||||
assertEquals(0, top.top)
|
||||
assertEquals(310, 1000 - top.top - top.bottom)
|
||||
assertEquals(15.5, CarPlayClusterDisplay.markerPercent(0, -3).second, 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theCarSitsNearThePanelCentre() {
|
||||
val safe = CarPlayClusterDisplay.SAFE_AREA_PERCENT
|
||||
val centreX = (safe.left + 100 - safe.right) / 2.0
|
||||
|
||||
assertTrue("centre x $centreX", centreX in 48.0..52.0)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user