i18n P2: resourceize 361 hardcoded UI strings in 3 activities (en + zh-rCN)

- Replace UI literals with getString(R.string.*) across DiPlayActivity,
  CarPlayHostActivity, ImageCropActivity (316 keys + 15 parameterized keys)
- Generate values/strings.xml (338 entries) and values-zh-rCN/strings.xml
- Parameterize templates (SSID/version/suffix prefixes) with format args
- Merge concatenated Android 9/MFi hint strings into single resources
- Protect leading/trailing spaces via \\u0020 so AAPT does not trim them
- Rename reserved resource name 'native' to 'resolution_native'
This commit is contained in:
baileileicn committed 2026-09-29 09:58:27 +08:00
1 parent 2b33c3de35
commit 9a4c03e2ec
5 files changed
+1026 -358

No files matched your search

@@ -160,7 +160,7 @@ class CarPlayHostActivity : ComponentActivity() {
vpnReady = true
maybeStartCarPlay()
} else {
setStatus("VPN consent was denied")
setStatus(getString(R.string.vpn_consent_was_denied))
}
}
private val wirelessPermissions =
@@ -777,38 +777,38 @@ class CarPlayHostActivity : ComponentActivity() {
isClickable = true
}
panel.addView(ImageView(this).apply {
setImageResource(R.drawable.ic_carplay); contentDescription = "CarPlay"
setImageResource(R.drawable.ic_carplay); contentDescription = getString(R.string.carplay)
}, LinearLayout.LayoutParams(dp(88), dp(88)))
panel.addView(TextView(this).apply {
text = "DiPlay"; textSize = 34f; setTextColor(Color.rgb(241, 245, 252))
text = getString(R.string.diplay); textSize = 34f; setTextColor(Color.rgb(241, 245, 252))
gravity = Gravity.CENTER; setPadding(0, dp(18), 0, dp(14))
typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
})
val stage = TextView(this).apply {
text = "Getting CarPlay ready…"; textSize = 22f; gravity = Gravity.CENTER
text = getString(R.string.getting_carplay_ready); textSize = 22f; gravity = Gravity.CENTER
setTextColor(Color.rgb(241, 245, 252))
}
panel.addView(stage)
panel.addView(TextView(this).apply {
text = if (wirelessEnabled) "Keep your iPhone nearby with Bluetooth and Wi-Fi on.\nAllow CarPlay if your iPhone asks."
else "Use a USB data cable and unlock your iPhone.\nAllow Trust and CarPlay if your iPhone asks."
text = if (wirelessEnabled) getString(R.string.keep_your_iphone_nearby_with_bluetooth_and_wi_fi_on_allow)
else getString(R.string.use_a_usb_data_cable_and_unlock_your_iphone_allow_trust_an)
textSize = 17f; gravity = Gravity.CENTER; setTextColor(Color.rgb(168, 182, 202))
setPadding(0, dp(14), 0, dp(24))
})
panel.addView(Button(this).apply {
text = "Reset CarPlay Wi-Fi"; isAllCaps = false; textSize = 18f
text = getString(R.string.reset_carplay_wi_fi); isAllCaps = false; textSize = 18f
visibility = View.GONE
setOnClickListener { showDiPlayHome("wireless-recovery") }
wifiRecoveryButton = this
}, LinearLayout.LayoutParams(dp(300), dp(64)).apply { bottomMargin = dp(12) })
panel.addView(Button(this).apply {
text = "Back to DiPlay"; isAllCaps = false; textSize = 18f
text = getString(R.string.back_to_diplay); isAllCaps = false; textSize = 18f
setTextColor(Color.rgb(12, 17, 27))
background = GradientDrawable().apply { setColor(Color.rgb(166, 200, 255)); cornerRadius = dp(20).toFloat() }
setOnClickListener { showDiPlayHome() }
}, LinearLayout.LayoutParams(dp(300), dp(64)))
panel.addView(TextView(this).apply {
text = "In CarPlay, swipe down with three fingers to open DiPlay settings."
text = getString(R.string.in_carplay_swipe_down_with_three_fingers_to_open_diplay_se)
textSize = 13f; gravity = Gravity.CENTER; setTextColor(Color.rgb(168, 182, 202)); setPadding(0, dp(20), 0, 0)
})
root.addView(panel, FrameLayout.LayoutParams(-1, -1))
@@ -834,7 +834,7 @@ class CarPlayHostActivity : ComponentActivity() {
setPadding(dp(48), dp(36), dp(48), dp(36))
}
content.addView(
menuText("CarPlay settings", 32f, Color.WHITE, bold = true).apply {
menuText(getString(R.string.carplay_settings), 32f, Color.WHITE, bold = true).apply {
setPadding(dp(56), 0, 0, 0)
},
LinearLayout.LayoutParams(
@@ -843,7 +843,7 @@ class CarPlayHostActivity : ComponentActivity() {
),
)
content.addView(
settingsCategoryHeader("Connection"),
settingsCategoryHeader(getString(R.string.connection)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -863,12 +863,12 @@ class CarPlayHostActivity : ComponentActivity() {
gravity = Gravity.CENTER_VERTICAL
}
wirelessRow.addView(
menuText("Wireless CarPlay", 20f, MENU_SECONDARY),
menuText(getString(R.string.wireless_carplay_2), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f),
)
val wirelessSwitch = Switch(this).apply {
isChecked = wirelessEnabled
contentDescription = "Wireless CarPlay transport"
contentDescription = getString(R.string.wireless_carplay_transport)
showText = false
thumbTintList = ColorStateList(
arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
@@ -914,7 +914,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
menuText("Hotspot status", 20f, MENU_SECONDARY),
menuText(getString(R.string.hotspot_status), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -930,7 +930,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
settingsCategoryHeader("Location"),
settingsCategoryHeader(getString(R.string.location)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -945,7 +945,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
settingsCategoryHeader("Startup"),
settingsCategoryHeader(getString(R.string.startup)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -953,9 +953,9 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
settingsSwitchRow(
label = "Auto-start on boot",
label = getString(R.string.auto_start_on_boot),
checked = autoStartOnBoot,
description = "Start CarPlay automatically after device boot",
description = getString(R.string.start_carplay_automatically_after_device_boot),
) { checked ->
autoStartOnBoot = checked
appendLog("Boot auto-start ${if (checked) "enabled" else "disabled"}")
@@ -968,7 +968,7 @@ class CarPlayHostActivity : ComponentActivity() {
if (advancedAudioChannelMappingSupported) {
content.addView(
settingsCategoryHeader("Audio"),
settingsCategoryHeader(getString(R.string.audio)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -976,9 +976,9 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
settingsSwitchRow(
label = "Advanced audio channel mapping",
label = getString(R.string.advanced_audio_channel_mapping),
checked = advancedAudioChannelMapping,
description = "Use usage / content-type routing instead of stream type",
description = getString(R.string.use_usage_content_type_routing_instead_of_stream_type),
) { checked ->
advancedAudioChannelMapping = checked
appendLog(
@@ -995,7 +995,7 @@ class CarPlayHostActivity : ComponentActivity() {
}
content.addView(
settingsCategoryHeader("Identity & appearance"),
settingsCategoryHeader(getString(R.string.identity_appearance)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1023,7 +1023,7 @@ class CarPlayHostActivity : ComponentActivity() {
).apply { topMargin = dp(26) },
)
content.addView(
settingsCategoryHeader("Display & video"),
settingsCategoryHeader(getString(R.string.display_video)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1035,7 +1035,7 @@ class CarPlayHostActivity : ComponentActivity() {
gravity = Gravity.CENTER_VERTICAL
}
resolutionHeader.addView(
menuText("Resolution", 20f, MENU_SECONDARY),
menuText(getString(R.string.resolution), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f),
)
val resolutionValue = menuText(
@@ -1091,11 +1091,11 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.HORIZONTAL
}
range.addView(
menuText("0.3x", 15f, MENU_SECONDARY),
menuText(getString(R.string.s_0_3x), 15f, MENU_SECONDARY),
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f),
)
range.addView(
menuText("1.0x", 15f, MENU_SECONDARY),
menuText(getString(R.string.s_1_0x), 15f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1111,7 +1111,7 @@ class CarPlayHostActivity : ComponentActivity() {
content.addView(
buildStepSliderSection(
title = "Frame rate",
title = getString(R.string.frame_rate),
values = (
AirPlayDisplaySettings.MIN_FPS..AirPlayDisplaySettings.MAX_FPS
step AirPlayDisplaySettings.FPS_STEP
@@ -1131,10 +1131,10 @@ class CarPlayHostActivity : ComponentActivity() {
content.addView(
settingsChoiceRow(
label = "Physical size basis",
label = getString(R.string.physical_size_basis),
options = listOf(
AirPlayPhysicalSizeBasis.WIDTH to "Widest width",
AirPlayPhysicalSizeBasis.HEIGHT to "Longest height",
AirPlayPhysicalSizeBasis.WIDTH to getString(R.string.widest_width),
AirPlayPhysicalSizeBasis.HEIGHT to getString(R.string.longest_height),
),
selected = physicalSizeBasis,
) { value ->
@@ -1149,7 +1149,7 @@ class CarPlayHostActivity : ComponentActivity() {
content.addView(
buildStepSliderSection(
title = "Physical length",
title = getString(R.string.physical_length),
values = (
AirPlayDisplaySettings.MIN_WIDTH_PHYSICAL_MM..
AirPlayDisplaySettings.MAX_WIDTH_PHYSICAL_MM
@@ -1178,7 +1178,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
val hevcSwitch = Switch(this).apply {
isChecked = hevcEnabled
contentDescription = "HEVC H.265 video transport"
contentDescription = getString(R.string.hevc_h_265_video_transport)
showText = false
thumbTintList = ColorStateList(
arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
@@ -1218,12 +1218,12 @@ class CarPlayHostActivity : ComponentActivity() {
gravity = Gravity.CENTER_VERTICAL
}
softwareHevcRow.addView(
menuText("HEVC software decoder", 20f, MENU_SECONDARY),
menuText(getString(R.string.hevc_software_decoder), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f),
)
val softwareHevcSwitch = Switch(this).apply {
isChecked = hevcSoftwareDecoderEnabled
contentDescription = "Use software HEVC decoder"
contentDescription = getString(R.string.use_software_hevc_decoder)
showText = false
thumbTintList = ColorStateList(
arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
@@ -1269,7 +1269,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
settingsCategoryHeader("Window"),
settingsCategoryHeader(getString(R.string.window)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1284,7 +1284,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
settingsCategoryHeader("Diagnostics"),
settingsCategoryHeader(getString(R.string.diagnostics)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1300,7 +1300,7 @@ class CarPlayHostActivity : ComponentActivity() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
content.addView(
settingsCategoryHeader("Android 9 compatibility"),
settingsCategoryHeader(getString(R.string.android_9_compatibility)),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1308,10 +1308,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
content.addView(
menuText(
"The following settings are unavailable and hidden on Android 9 " +
"(API 28):\n" +
"• Wi-Fi P2P (5 GHz) — LocalOnlyHotspot is used instead.\n" +
"• HEVC software decoder — hardware decoding is used instead.",
getString(R.string.settings_android9_compat),
16f,
MENU_SECONDARY,
),
@@ -1332,7 +1329,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
val save = Button(this).apply {
text = "Save and reconnect"
text = getString(R.string.save_and_reconnect)
isAllCaps = false
textSize = 17f
setTextColor(MENU_BUTTON_TEXT)
@@ -1349,7 +1346,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
val exitApplicationButton = Button(this).apply {
text = "EXIT APPLICATION"
text = getString(R.string.exit_application)
isAllCaps = false
textSize = 17f
setTextColor(Color.WHITE)
@@ -1389,7 +1386,7 @@ class CarPlayHostActivity : ComponentActivity() {
textSize = 22f
setTextColor(Color.WHITE)
backgroundTintList = ColorStateList.valueOf(MENU_TRACK_OFF)
contentDescription = "Discard changes and exit settings"
contentDescription = getString(R.string.discard_changes_and_exit_settings)
minWidth = 0
minHeight = 0
setPadding(0, 0, 0, 0)
@@ -1518,11 +1515,11 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
val targetChoice = settingsChoiceRow(
label = "MFI certificate & signing target",
label = getString(R.string.mfi_certificate_signing_target),
options = listOf(
MfiTarget.USB_CH341 to "USB/CH341",
MfiTarget.I2C to "I2C",
MfiTarget.REMOTE to "Remote",
MfiTarget.USB_CH341 to getString(R.string.usb_ch341),
MfiTarget.I2C to getString(R.string.i2c),
MfiTarget.REMOTE to getString(R.string.remote),
),
selected = mfiTarget,
) { target ->
@@ -1544,7 +1541,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
addView(
settingsInputRow(
"I2C device",
getString(R.string.i2c_device),
mfiI2cPath,
onInputCreated = { mfiI2cPathInput = it },
) { value ->
@@ -1557,7 +1554,7 @@ class CarPlayHostActivity : ComponentActivity() {
),
)
addView(
menuText("Linux device path, for example /dev/i2c-1.", 14f, MENU_SECONDARY),
menuText(getString(R.string.linux_device_path_for_example_dev_i2c_1), 14f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1577,7 +1574,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
addView(
settingsInputRow(
"Server address",
getString(R.string.server_address),
remoteMfiServer,
onInputCreated = { remoteMfiServerInput = it },
) { value ->
@@ -1590,7 +1587,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
addView(
settingsInputRow(
"Token (optional)",
getString(R.string.token_optional),
remoteMfiToken,
password = true,
onInputCreated = { remoteMfiTokenInput = it },
@@ -1604,8 +1601,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
addView(
menuText(
"Address must start with http:// or https://. Token is optional and sent " +
"as an Authorization bearer token.",
getString(R.string.settings_mfi_address_hint),
14f,
MENU_SECONDARY,
),
@@ -1665,10 +1661,10 @@ class CarPlayHostActivity : ComponentActivity() {
}
private fun mfiTargetLabel(target: MfiTarget): String = when (target) {
MfiTarget.LOCAL -> "Local offline"
MfiTarget.USB_CH341 -> "USB/CH341"
MfiTarget.I2C -> "I2C"
MfiTarget.REMOTE -> "Remote"
MfiTarget.LOCAL -> getString(R.string.local_offline)
MfiTarget.USB_CH341 -> getString(R.string.usb_ch341)
MfiTarget.I2C -> getString(R.string.i2c)
MfiTarget.REMOTE -> getString(R.string.remote)
}
private fun buildIdentitySettingsSection(): View {
@@ -1676,7 +1672,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
section.addView(
settingsInputRow("Manufacturer", manufacturer) { value ->
settingsInputRow(getString(R.string.manufacturer), manufacturer) { value ->
manufacturer = value
updateResolutionMenu()
},
@@ -1686,7 +1682,7 @@ class CarPlayHostActivity : ComponentActivity() {
),
)
section.addView(
settingsInputRow("Model", model) { value ->
settingsInputRow(getString(R.string.model), model) { value ->
model = value
updateResolutionMenu()
},
@@ -1696,7 +1692,7 @@ class CarPlayHostActivity : ComponentActivity() {
).apply { topMargin = dp(10) },
)
section.addView(
settingsInputRow("OEM label", oemLabel) { value ->
settingsInputRow(getString(R.string.oem_label), oemLabel) { value ->
oemLabel = value
updateResolutionMenu()
},
@@ -1719,12 +1715,12 @@ class CarPlayHostActivity : ComponentActivity() {
gravity = Gravity.CENTER_VERTICAL
}
row.addView(
menuText("Report location to iPhone", 20f, MENU_SECONDARY),
menuText(getString(R.string.report_location_to_iphone), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f),
)
val switch = Switch(this@CarPlayHostActivity).apply {
isChecked = locationReportingEnabled
contentDescription = "Report Android location to the iPhone"
contentDescription = getString(R.string.report_android_location_to_the_iphone)
showText = false
thumbTintList = ColorStateList(
arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
@@ -1755,7 +1751,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
addView(
menuText(
"Sends precise Android location as CarPlay GPS data when the iPhone requests it.",
getString(R.string.sends_precise_android_location_as_carplay_gps_data_when_th),
14f,
MENU_SECONDARY,
),
@@ -1781,9 +1777,9 @@ class CarPlayHostActivity : ComponentActivity() {
private fun buildDebugLogsSection(): View =
settingsSwitchRow(
label = "Debug logs",
label = getString(R.string.debug_logs),
checked = debugLogsEnabled,
description = "Show on-screen debug logs",
description = getString(R.string.show_on_screen_debug_logs),
) { checked ->
debugLogsEnabled = checked
appendLog("Debug logs ${if (debugLogsEnabled) "enabled" else "disabled"}")
@@ -1860,7 +1856,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
section.addView(
menuText("AirPlay icon", 20f, MENU_SECONDARY),
menuText(getString(R.string.airplay_icon), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1887,7 +1883,7 @@ class CarPlayHostActivity : ComponentActivity() {
}
actions.addView(
Button(this).apply {
text = "Choose image"
text = getString(R.string.choose_image)
isAllCaps = false
setOnClickListener {
externalActivityInProgress = true
@@ -1901,7 +1897,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
actions.addView(
Button(this).apply {
text = "Default icon"
text = getString(R.string.default_icon)
isAllCaps = false
setOnClickListener {
AirPlayPersistence.clearCustomAirPlayIcon(this@CarPlayHostActivity)
@@ -1947,7 +1943,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
section.addView(
menuText("Driving side", 20f, MENU_SECONDARY),
menuText(getString(R.string.driving_side), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1958,13 +1954,13 @@ class CarPlayHostActivity : ComponentActivity() {
}
val left = RadioButton(this).apply {
id = View.generateViewId()
text = "Left-hand drive"
text = getString(R.string.left_hand_drive)
setTextColor(Color.WHITE)
isChecked = !rightHandDrive
}
val right = RadioButton(this).apply {
id = View.generateViewId()
text = "Right-hand drive"
text = getString(R.string.right_hand_drive)
setTextColor(Color.WHITE)
isChecked = rightHandDrive
}
@@ -1989,7 +1985,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
section.addView(
menuText("Fullscreen", 20f, MENU_SECONDARY),
menuText(getString(R.string.fullscreen), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -1997,9 +1993,9 @@ class CarPlayHostActivity : ComponentActivity() {
)
section.addView(
settingsSwitchRow(
label = "Hide top bar",
label = getString(R.string.hide_top_bar),
checked = hideTopBar,
description = "Hide the status bar",
description = getString(R.string.hide_the_status_bar),
) { checked ->
hideTopBar = checked
applyFullscreenMode()
@@ -2012,9 +2008,9 @@ class CarPlayHostActivity : ComponentActivity() {
)
section.addView(
settingsSwitchRow(
label = "Hide bottom bar",
label = getString(R.string.hide_bottom_bar),
checked = hideBottomBar,
description = "Hide the navigation bar",
description = getString(R.string.hide_the_navigation_bar),
) { checked ->
hideBottomBar = checked
applyFullscreenMode()
@@ -2033,7 +2029,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
section.addView(
menuText("Safe area", 20f, MENU_SECONDARY),
menuText(getString(R.string.safe_area), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -2052,7 +2048,7 @@ class CarPlayHostActivity : ComponentActivity() {
}
buttons.addView(
Button(this).apply {
text = "Set"
text = getString(R.string.set)
isAllCaps = false
setOnClickListener { openSafeAreaEditor() }
},
@@ -2060,7 +2056,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
buttons.addView(
Button(this).apply {
text = "Reset"
text = getString(R.string.reset)
isAllCaps = false
setOnClickListener { resetSafeAreaForCurrentSize() }
},
@@ -2077,9 +2073,9 @@ class CarPlayHostActivity : ComponentActivity() {
)
section.addView(
settingsSwitchRow(
label = "Draw outside safe area",
label = getString(R.string.draw_outside_safe_area),
checked = safeAreaDrawOutside,
description = "Allow CarPlay UI outside the safe area",
description = getString(R.string.allow_carplay_ui_outside_the_safe_area),
) { checked ->
safeAreaDrawOutside = checked
updateResolutionMenu()
@@ -2108,7 +2104,7 @@ class CarPlayHostActivity : ComponentActivity() {
),
)
overlay.addView(
menuText("Safe area", 24f, Color.WHITE, bold = true).apply {
menuText(getString(R.string.safe_area), 24f, Color.WHITE, bold = true).apply {
setPadding(dp(16), dp(12), dp(16), dp(8))
},
FrameLayout.LayoutParams(
@@ -2124,7 +2120,7 @@ class CarPlayHostActivity : ComponentActivity() {
}
controls.addView(
Button(this).apply {
text = "Cancel"
text = getString(R.string.cancel)
isAllCaps = false
setOnClickListener { closeSafeAreaEditor() }
},
@@ -2132,7 +2128,7 @@ class CarPlayHostActivity : ComponentActivity() {
)
controls.addView(
Button(this).apply {
text = "Save"
text = getString(R.string.save)
isAllCaps = false
setOnClickListener { saveSafeAreaEditor() }
},
@@ -2258,7 +2254,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
section.addView(
menuText("Wi-Fi session", 20f, MENU_SECONDARY),
menuText(getString(R.string.wi_fi_session), 20f, MENU_SECONDARY),
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -2271,7 +2267,7 @@ class CarPlayHostActivity : ComponentActivity() {
}
val modes = buildList {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
add(WirelessHotspotMode.WIFI_P2P to "Wi-Fi P2P (5 GHz)")
add(WirelessHotspotMode.WIFI_P2P to getString(R.string.wi_fi_p2p_5_ghz))
}
add(WirelessHotspotMode.MANUAL to "Built-in car hotspot")
}
@@ -2325,7 +2321,7 @@ class CarPlayHostActivity : ComponentActivity() {
orientation = LinearLayout.VERTICAL
}
manualFields.addView(
settingsInputRow("Hotspot SSID", manualHotspotSsid) { value ->
settingsInputRow(getString(R.string.hotspot_ssid), manualHotspotSsid) { value ->
manualHotspotSsid = value
manualHotspotErrorView?.visibility = View.GONE
},
@@ -2337,11 +2333,11 @@ class CarPlayHostActivity : ComponentActivity() {
manualFields.addView(
settingsChoiceRow(
label = "Band",
label = getString(R.string.band),
options = listOf(
ManualHotspotBand.AUTO to "Auto",
ManualHotspotBand.GHZ_2_4 to "2.4 GHz",
ManualHotspotBand.GHZ_5 to "5 GHz",
ManualHotspotBand.AUTO to getString(R.string.auto),
ManualHotspotBand.GHZ_2_4 to getString(R.string.s_2_4_ghz),
ManualHotspotBand.GHZ_5 to getString(R.string.s_5_ghz),
),
selected = manualHotspotBand,
) { value ->
@@ -2356,7 +2352,7 @@ class CarPlayHostActivity : ComponentActivity() {
manualFields.addView(
settingsInputRow(
label = "Channel (0 = auto)",
label = getString(R.string.channel_0_auto),
value = manualHotspotChannel.toString(),
numeric = true,
) { value ->
@@ -2371,7 +2367,7 @@ class CarPlayHostActivity : ComponentActivity() {
manualFields.addView(
settingsInputRow(
label = "Hotspot password",
label = getString(R.string.hotspot_password),
value = manualHotspotPassphrase,
password = true,
) { value ->
@@ -2386,12 +2382,12 @@ class CarPlayHostActivity : ComponentActivity() {
manualFields.addView(
settingsChoiceRow(
label = "Security",
label = getString(R.string.security),
options = listOf(
ManualHotspotSecurity.OPEN to "Open",
ManualHotspotSecurity.WPA2 to "WPA2",
ManualHotspotSecurity.WPA3_TRANSITION to "WPA3 transition",
ManualHotspotSecurity.WPA3 to "WPA3",
ManualHotspotSecurity.OPEN to getString(R.string.open),
ManualHotspotSecurity.WPA2 to getString(R.string.wpa2),
ManualHotspotSecurity.WPA3_TRANSITION to getString(R.string.wpa3_transition),
ManualHotspotSecurity.WPA3 to getString(R.string.wpa3),
),
selected = manualHotspotSecurity,
) { value ->
@@ -2437,16 +2433,16 @@ class CarPlayHostActivity : ComponentActivity() {
private fun validateMfiSettings(): Boolean {
val error = when {
mfiTarget == MfiTarget.I2C && mfiI2cPath.isBlank() ->
"I2C device path is required"
getString(R.string.i2c_device_path_is_required)
mfiTarget == MfiTarget.REMOTE && remoteMfiServer.isBlank() ->
"Remote server address is required"
getString(R.string.remote_server_address_is_required)
mfiTarget == MfiTarget.REMOTE &&
!remoteMfiServer.trim().startsWith("http://") &&
!remoteMfiServer.trim().startsWith("https://") ->
"Remote server address must start with http:// or https://"
'\u0000' in mfiI2cPath -> "I2C device path contains U+0000"
'\u0000' in remoteMfiServer -> "Remote server address contains U+0000"
'\u0000' in remoteMfiToken -> "Remote token contains U+0000"
getString(R.string.remote_server_address_must_start_with_http_or_https)
'\u0000' in mfiI2cPath -> getString(R.string.i2c_device_path_contains_u_0000)
'\u0000' in remoteMfiServer -> getString(R.string.remote_server_address_contains_u_0000)
'\u0000' in remoteMfiToken -> getString(R.string.remote_token_contains_u_0000)
else -> null
}
mfiErrorView?.text = error.orEmpty()
@@ -2457,21 +2453,21 @@ class CarPlayHostActivity : ComponentActivity() {
private fun validateManualHotspotSettings(): Boolean {
if (wirelessHotspotMode != WirelessHotspotMode.MANUAL) return true
val error = when {
manualHotspotSsid.isBlank() -> "Hotspot SSID is required"
manualHotspotSsid.isBlank() -> getString(R.string.hotspot_ssid_is_required)
manualHotspotSsid.encodeToByteArray().size > 32 ->
"Hotspot SSID must be at most 32 UTF-8 bytes"
'\u0000' in manualHotspotSsid -> "Hotspot SSID contains U+0000"
manualHotspotChannel !in 0..196 -> "Channel must be 0 or 1-196"
getString(R.string.hotspot_ssid_must_be_at_most_32_utf_8_bytes)
'\u0000' in manualHotspotSsid -> getString(R.string.hotspot_ssid_contains_u_0000)
manualHotspotChannel !in 0..196 -> getString(R.string.channel_must_be_0_or_1_196)
manualHotspotChannel != 0 &&
!isManualHotspotChannelCompatible(manualHotspotBand, manualHotspotChannel) ->
"Channel is not valid for the selected band"
'\u0000' in manualHotspotPassphrase -> "Hotspot password contains U+0000"
getString(R.string.channel_is_not_valid_for_the_selected_band)
'\u0000' in manualHotspotPassphrase -> getString(R.string.hotspot_password_contains_u_0000)
manualHotspotSecurity == ManualHotspotSecurity.OPEN &&
manualHotspotPassphrase.isNotEmpty() ->
"Password must be empty when security is Open"
getString(R.string.password_must_be_empty_when_security_is_open)
manualHotspotSecurity != ManualHotspotSecurity.OPEN &&
manualHotspotPassphrase.length !in 8..63 ->
"WPA2/WPA3 password must be 8-63 characters"
getString(R.string.wpa2_wpa3_password_must_be_8_63_characters)
else -> null
}
manualHotspotErrorView?.text = error.orEmpty()
@@ -2480,9 +2476,9 @@ class CarPlayHostActivity : ComponentActivity() {
}
private fun hotspotModeLabel(mode: WirelessHotspotMode): String = when (mode) {
WirelessHotspotMode.WIFI_P2P -> "Wi-Fi P2P (5 GHz)"
WirelessHotspotMode.LOCAL_ONLY_HOTSPOT -> "LocalOnlyHotspot"
WirelessHotspotMode.MANUAL -> "Manual hotspot"
WirelessHotspotMode.WIFI_P2P -> getString(R.string.wi_fi_p2p_5_ghz)
WirelessHotspotMode.LOCAL_ONLY_HOTSPOT -> getString(R.string.localonlyhotspot)
WirelessHotspotMode.MANUAL -> getString(R.string.manual_hotspot)
}
private fun menuText(
@@ -2501,25 +2497,25 @@ class CarPlayHostActivity : ComponentActivity() {
private fun updateHotspotStatus(status: CarPlayStatus) {
if (!wirelessEnabled) return
hotspotStatus = when (status) {
CarPlayStatus.StartingHotspot -> HotspotStatus(state = "Starting")
CarPlayStatus.StartingHotspot -> HotspotStatus(state = getString(R.string.starting))
is CarPlayStatus.HotspotReady -> HotspotStatus(
state = "Ready",
state = getString(R.string.ready),
ssid = status.ssid,
band = status.band,
channel = status.channel,
backend = status.backend,
)
CarPlayStatus.WaitingForPairedIphone ->
hotspotStatus.copy(state = "Waiting for paired iPhone")
hotspotStatus.copy(state = getString(R.string.waiting_for_paired_iphone))
CarPlayStatus.ConnectingBluetooth ->
hotspotStatus.copy(state = "Connecting Bluetooth")
hotspotStatus.copy(state = getString(R.string.connecting_bluetooth))
CarPlayStatus.RunningWireless ->
hotspotStatus.copy(state = "Running")
hotspotStatus.copy(state = getString(R.string.running))
CarPlayStatus.WirelessActive ->
hotspotStatus.copy(state = "Active")
hotspotStatus.copy(state = getString(R.string.active))
CarPlayStatus.AttachingNetwork ->
hotspotStatus.copy(state = "Starting AirPlay service")
is CarPlayStatus.Failed -> hotspotStatus.copy(state = "Error")
hotspotStatus.copy(state = getString(R.string.starting_airplay_service))
is CarPlayStatus.Failed -> hotspotStatus.copy(state = getString(R.string.error))
else -> return
}
updateHotspotStatusBlock()
@@ -2527,7 +2523,7 @@ class CarPlayHostActivity : ComponentActivity() {
private fun updateHotspotStatusBlock() {
if (!wirelessEnabled) {
hotspotStatusView?.text = "Wireless hotspot: off"
hotspotStatusView?.text = getString(R.string.wireless_hotspot_off)
return
}
val status = hotspotStatus
@@ -2602,7 +2598,7 @@ class CarPlayHostActivity : ComponentActivity() {
resolutionValueView?.text = CarPlayDisplayScale.label(displayScaleTenths)
val native = activeDisplaySize ?: currentActivitySize()
val resolution = if (native == null) {
"Handshake resolution: waiting for display"
getString(R.string.handshake_resolution_waiting_for_display)
} else {
val negotiated = CarPlayDisplayScale.apply(
AirPlayDisplayConfig(
@@ -2613,7 +2609,7 @@ class CarPlayHostActivity : ComponentActivity() {
),
displayScaleTenths,
)
"Handshake resolution: ${native.width} x ${native.height} -> " +
"${getString(R.string.resolution_handshake_prefix)}${native.width} x ${native.height} -> " +
"${negotiated.widthPixels} x ${negotiated.heightPixels}"
}
val transport = if (!hevcEnabled) {
@@ -2727,7 +2723,7 @@ class CarPlayHostActivity : ComponentActivity() {
appendLog("Larger CarPlay canvas unavailable reason=${support.reason}; using Default icon and text size")
runOnUiThread {
android.widget.Toast.makeText(this,
"This head unit cannot use the smaller size at this resolution. Using Default.",
getString(R.string.this_head_unit_cannot_use_the_smaller_size_at_this_resolut),
android.widget.Toast.LENGTH_LONG).show()
}
}
@@ -2814,7 +2810,7 @@ class CarPlayHostActivity : ComponentActivity() {
val bitmap = customBitmap ?: BitmapFactory.decodeResource(resources, R.raw.placeholder_icon)
preview.setImageBitmap(bitmap)
iconStatusView?.text =
if (customBitmap != null) "Custom 1:1 icon" else "Default placeholder icon"
if (customBitmap != null) getString(R.string.custom_1_1_icon) else getString(R.string.default_placeholder_icon)
}
private fun currentActivitySize(): DisplaySize? {
@@ -2836,12 +2832,12 @@ class CarPlayHostActivity : ComponentActivity() {
)
private fun safeAreaSummary(): String {
val size = currentActivitySize() ?: return "Safe area: waiting for activity size"
val size = currentActivitySize() ?: return getString(R.string.safe_area_waiting_for_activity_size)
val mapping = AirPlayPersistence.loadSafeAreaRect(this, size.width, size.height)
return if (mapping == null) {
"Safe area: full screen at ${size.width} x ${size.height}"
"${getString(R.string.safe_area_full_screen_at)}${size.width} x ${size.height}"
} else {
"Safe area: ${mapping.width} x ${mapping.height} at " +
"${getString(R.string.safe_area_prefix)}${mapping.width} x ${mapping.height} at " +
"(${mapping.left}, ${mapping.top}) in ${size.width} x ${size.height}"
}
}
@@ -2973,7 +2969,7 @@ class CarPlayHostActivity : ComponentActivity() {
return@runOnUiThread
}
activeScreenStreamTypes.clear()
setConnectionStage("CarPlay session ended; reconnecting")
setConnectionStage(getString(R.string.carplay_session_ended_reconnecting))
appendLog("AirPlay session ended; reconnecting from scratch")
reconnectAfterLoss("AirPlay session ended")
}
@@ -2985,7 +2981,7 @@ class CarPlayHostActivity : ComponentActivity() {
return@runOnUiThread
}
activeScreenStreamTypes.clear()
setConnectionStage("Transport error; reconnecting")
setConnectionStage(getString(R.string.transport_error_reconnecting))
appendLog("CarPlay transport error: $message; reconnecting from scratch")
reconnectAfterLoss("CarPlay transport error: $message")
}
@@ -3064,9 +3060,9 @@ class CarPlayHostActivity : ComponentActivity() {
)
setConnectionStage(
if (serviceReused) {
"CarPlay service already running"
getString(R.string.carplay_service_already_running)
} else {
"CarPlay session already running"
getString(R.string.carplay_session_already_running)
},
)
updateDebugOverlays()
@@ -3147,7 +3143,7 @@ class CarPlayHostActivity : ComponentActivity() {
} catch (error: RuntimeException) {
appendLog("Connection could not start: ${error.javaClass.simpleName}")
shutdown(false, "foreground service could not start")
setConnectionStage("Could not start CarPlay. Return to DiPlay and check app permissions.")
setConnectionStage(getString(R.string.could_not_start_carplay_return_to_diplay_and_check_app_per))
}
}
@@ -3489,21 +3485,21 @@ class CarPlayHostActivity : ComponentActivity() {
}
private fun friendlyStage(message: String): String = when {
message.contains("Turn on Wi-Fi", true) -> "Turn on Wi-Fi in the head unit’s settings to connect."
message.contains("Allow precise Location", true) -> "Allow precise Location for DiPlay in the head unit’s app permissions."
message.contains("Allow Nearby devices", true) -> "Allow Nearby devices for DiPlay in the head unit’s app permissions."
message.contains("createGroup failed", true) -> "The head unit couldn’t start CarPlay Wi-Fi. Check Wi-Fi and close other projection apps. Retrying…"
message.contains("needs a reset", true) -> "A previous Wi-Fi Direct connection is still running. Reset it to connect."
message.contains("socket", true) || message.contains("RFCOMM", true) -> "Your iPhone isn’t available. Unlock it and check Bluetooth."
message.contains("unsupported", true) || message.contains("not supported", true) -> "This head unit may not support wireless CarPlay. Try a USB connection."
message.contains("denied", true) || message.contains("permission", true) -> "Allow the connection permission to continue"
message.contains("Failed", true) || message.contains("error", true) -> "Connection interrupted. Retrying…"
message.contains("Waiting for iPhone", true) || message.contains("Discovering iPhone", true) -> "Connect your iPhone with a USB cable"
message.contains("paired", true) -> "Looking for your paired iPhone…"
message.contains("Bluetooth", true) -> "Connecting to your iPhone…"
message.contains("reconnect", true) || message.contains("ended", true) -> "Reconnecting to your iPhone…"
message.contains("active", true) || message.contains("running", true) -> "Opening CarPlay…"
else -> "Getting CarPlay ready…"
message.contains("Turn on Wi-Fi", true) -> getString(R.string.turn_on_wi_fi_in_the_head_unit_s_settings_to_connect)
message.contains("Allow precise Location", true) -> getString(R.string.allow_precise_location_for_diplay_in_the_head_unit_s_app_p)
message.contains("Allow Nearby devices", true) -> getString(R.string.allow_nearby_devices_for_diplay_in_the_head_unit_s_app_per)
message.contains("createGroup failed", true) -> getString(R.string.the_head_unit_couldn_t_start_carplay_wi_fi_check_wi_fi_and)
message.contains("needs a reset", true) -> getString(R.string.a_previous_wi_fi_direct_connection_is_still_running_reset)
message.contains("socket", true) || message.contains("RFCOMM", true) -> getString(R.string.your_iphone_isn_t_available_unlock_it_and_check_bluetooth)
message.contains("unsupported", true) || message.contains("not supported", true) -> getString(R.string.this_head_unit_may_not_support_wireless_carplay_try_a_usb)
message.contains("denied", true) || message.contains("permission", true) -> getString(R.string.allow_the_connection_permission_to_continue)
message.contains("Failed", true) || message.contains("error", true) -> getString(R.string.connection_interrupted_retrying)
message.contains("Waiting for iPhone", true) || message.contains("Discovering iPhone", true) -> getString(R.string.connect_your_iphone_with_a_usb_cable)
message.contains("paired", true) -> getString(R.string.looking_for_your_paired_iphone)
message.contains("Bluetooth", true) -> getString(R.string.connecting_to_your_iphone)
message.contains("reconnect", true) || message.contains("ended", true) -> getString(R.string.reconnecting_to_your_iphone)
message.contains("active", true) || message.contains("running", true) -> getString(R.string.opening_carplay)
else -> getString(R.string.getting_carplay_ready)
}
private fun appendLog(message: String) {
@@ -3575,30 +3571,30 @@ class CarPlayHostActivity : ComponentActivity() {
private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt()
private fun CarPlayStatus.describe(): String = when (this) {
CarPlayStatus.DiscoveringMfi -> "Preparing MFi authentication"
CarPlayStatus.WaitingForMfi -> "Waiting for MFi coprocessor"
CarPlayStatus.RequestingMfiPermission -> "Requesting MFi USB permission"
CarPlayStatus.MfiReady -> "MFi authentication ready"
CarPlayStatus.StartingHotspot -> "Starting wireless hotspot"
CarPlayStatus.DiscoveringMfi -> getString(R.string.preparing_mfi_authentication)
CarPlayStatus.WaitingForMfi -> getString(R.string.waiting_for_mfi_coprocessor)
CarPlayStatus.RequestingMfiPermission -> getString(R.string.requesting_mfi_usb_permission)
CarPlayStatus.MfiReady -> getString(R.string.mfi_authentication_ready)
CarPlayStatus.StartingHotspot -> getString(R.string.starting_wireless_hotspot)
is CarPlayStatus.HotspotReady ->
"Hotspot ready: $backend, $ssid, $band, " +
"channel ${if (channel == 0) "auto" else channel}"
CarPlayStatus.WaitingForPairedIphone -> "Waiting for paired iPhone"
CarPlayStatus.ConnectingBluetooth -> "Connecting Bluetooth"
CarPlayStatus.RunningWireless -> "Wireless CarPlay control running"
CarPlayStatus.WirelessActive -> "Wireless CarPlay active"
CarPlayStatus.DiscoveringIphone -> "Discovering iPhone"
CarPlayStatus.WaitingForIphone -> "Waiting for iPhone over USB"
CarPlayStatus.RequestingIphonePermission -> "Requesting iPhone USB permission"
CarPlayStatus.WaitingForPairedIphone -> getString(R.string.waiting_for_paired_iphone)
CarPlayStatus.ConnectingBluetooth -> getString(R.string.connecting_bluetooth)
CarPlayStatus.RunningWireless -> getString(R.string.wireless_carplay_control_running)
CarPlayStatus.WirelessActive -> getString(R.string.wireless_carplay_active)
CarPlayStatus.DiscoveringIphone -> getString(R.string.discovering_iphone)
CarPlayStatus.WaitingForIphone -> getString(R.string.waiting_for_iphone_over_usb)
CarPlayStatus.RequestingIphonePermission -> getString(R.string.requesting_iphone_usb_permission)
CarPlayStatus.WaitingForReenumeration -> "Waiting for iPhone re-enumeration"
CarPlayStatus.SelectingConfiguration -> "Selecting CarPlay configuration"
CarPlayStatus.OpeningDataPaths -> "Opening USB data paths"
CarPlayStatus.Pairing -> "Pairing with iPhone"
CarPlayStatus.ConnectingControl -> "Connecting iAP2 control"
CarPlayStatus.SelectingConfiguration -> getString(R.string.selecting_carplay_configuration)
CarPlayStatus.OpeningDataPaths -> getString(R.string.opening_usb_data_paths)
CarPlayStatus.Pairing -> getString(R.string.pairing_with_iphone)
CarPlayStatus.ConnectingControl -> getString(R.string.connecting_iap2_control)
CarPlayStatus.AttachingNetwork ->
if (wirelessEnabled) "Starting AirPlay service" else "Attaching NCM/AirPlay network"
CarPlayStatus.RunningControl -> "CarPlay control running"
CarPlayStatus.ControlEnded -> "CarPlay control window ended"
if (wirelessEnabled) getString(R.string.starting_airplay_service) else "Attaching NCM/AirPlay network"
CarPlayStatus.RunningControl -> getString(R.string.carplay_control_running)
CarPlayStatus.ControlEnded -> getString(R.string.carplay_control_window_ended)
is CarPlayStatus.Failed -> "Failed: ${message}"
}
@@ -65,7 +65,7 @@ class DiPlayActivity : ComponentActivity() {
override fun run() { refreshStatus(); handler.postDelayed(this, 1000) }
}
private val bluetoothPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) choosePhone() else permissionHelp("Nearby devices", "Allow Nearby devices so DiPlay can connect to your paired iPhone.")
if (granted) choosePhone() else permissionHelp(getString(R.string.nearby_devices), getString(R.string.allow_nearby_devices_so_diplay_can_connect_to_your_paired))
}
private val export = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
if (uri != null) exportDiagnostics(uri)
@@ -127,9 +127,9 @@ class DiPlayActivity : ComponentActivity() {
val content = column().apply { setPadding(dp(32), dp(24), dp(32), dp(32)) }
scroll.addView(content)
val header = row().apply { gravity = Gravity.CENTER_VERTICAL }
header.addView(ImageView(this).apply { setImageResource(R.drawable.ic_carplay); contentDescription = "CarPlay" }, LinearLayout.LayoutParams(dp(36), dp(36)))
header.addView(label("DiPlay", 26, TEXT, true).apply { setPadding(dp(12), 0, 0, 0) }, LinearLayout.LayoutParams(0, dp(56), 1f))
header.addView(button(if (page == "home") "Car home" else "Back", false) {
header.addView(ImageView(this).apply { setImageResource(R.drawable.ic_carplay); contentDescription = getString(R.string.carplay) }, LinearLayout.LayoutParams(dp(36), dp(36)))
header.addView(label(getString(R.string.diplay), 26, TEXT, true).apply { setPadding(dp(12), 0, 0, 0) }, LinearLayout.LayoutParams(0, dp(56), 1f))
header.addView(button(if (page == "home") getString(R.string.car_home) else getString(R.string.back), false) {
if (page == "home") startActivity(Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME))
else { page = "home"; render() }
}, LinearLayout.LayoutParams(dp(130), dp(56)))
@@ -149,14 +149,14 @@ class DiPlayActivity : ComponentActivity() {
val wide = resources.configuration.screenWidthDp >= 850
val body = column()
val left = column()
left.addView(label("YOUR PHONE. YOUR DRIVE.", 12, ACCENT, true).apply { letterSpacing = .16f })
left.addView(label("A familiar drive.", if (wide) 42 else 36, TEXT, true).apply { setPadding(0, dp(12), 0, dp(10)) })
left.addView(label("Your maps, music and conversations.\nCarPlay, right here on your car display.", 19, MUTED))
left.addView(label(getString(R.string.your_phone_your_drive), 12, ACCENT, true).apply { letterSpacing = .16f })
left.addView(label(getString(R.string.a_familiar_drive), if (wide) 42 else 36, TEXT, true).apply { setPadding(0, dp(12), 0, dp(10)) })
left.addView(label(getString(R.string.your_maps_music_and_conversations_carplay_right_here_on_yo), 19, MUTED))
val card = card()
card.addView(label("WIRELESS CARPLAY", 12, ACCENT, true).apply { letterSpacing = .12f })
status = label("Ready when you are", 24, TEXT, true).apply { setPadding(0, dp(10), 0, dp(16)) }
card.addView(label(getString(R.string.wireless_carplay), 12, ACCENT, true).apply { letterSpacing = .12f })
status = label(getString(R.string.ready_when_you_are), 24, TEXT, true).apply { setPadding(0, dp(10), 0, dp(16)) }
card.addView(status)
connectButton = button("Connect phone", true) {
connectButton = button(getString(R.string.connect_phone), true) {
if (CarPlayBackgroundSession.hasSession()) openProjection()
else connect(true)
}
@@ -168,11 +168,11 @@ class DiPlayActivity : ComponentActivity() {
}
card.addView(label(connectionHint, 15, MUTED).apply { setPadding(0, dp(14), 0, 0) })
if (carHotspotOff()) {
card.addView(label("The car hotspot “${AirPlayPersistence.loadManualHotspotSsid(this)}” is off. Turn it on in the car settings before connecting.", 15, WARNING).apply { setPadding(0, dp(14), 0, 0) })
card.addView(button("Open car hotspot settings", false) { openCarWifiSettings() }, matchButton(10, 56))
card.addView(label(getString(R.string.msg_car_hotspot_off, AirPlayPersistence.loadManualHotspotSsid(this)), 15, WARNING).apply { setPadding(0, dp(14), 0, 0) })
card.addView(button(getString(R.string.open_car_hotspot_settings), false) { openCarWifiSettings() }, matchButton(10, 56))
}
card.addView(button("Choose iPhone", false) { choosePhone() }, matchButton(16, 56))
disconnectButton = button("Disconnect", false) {
card.addView(button(getString(R.string.choose_iphone), false) { choosePhone() }, matchButton(16, 56))
disconnectButton = button(getString(R.string.disconnect), false) {
disconnectButton?.isEnabled = false
CarPlayBackgroundSession.stop { runOnUiThread { refreshStatus() } }
}.apply { visibility = View.GONE }
@@ -180,18 +180,18 @@ class DiPlayActivity : ComponentActivity() {
val right = column().apply { gravity = Gravity.CENTER_HORIZONTAL }
val logo = ImageView(this).apply {
setImageResource(R.drawable.ic_carplay)
contentDescription = "CarPlay icon"
contentDescription = getString(R.string.carplay_icon)
scaleType = ImageView.ScaleType.FIT_CENTER
}
val branding = column().apply {
gravity = Gravity.CENTER
addView(logo, LinearLayout.LayoutParams(dp(96), dp(96)))
}
right.addView(button("Connect with USB", false) { connect(false) }, matchButton())
right.addView(label("Plug your iPhone into a USB data port.\nAllow CarPlay when your iPhone asks.", 14, MUTED).apply { gravity = Gravity.CENTER; setPadding(dp(8), dp(10), dp(8), dp(24)) })
right.addView(button("Settings", false) { page = "settings"; render() }, matchButton())
right.addView(label("Make DiPlay feel right for your car.", 14, MUTED).apply { gravity = Gravity.CENTER; setPadding(0, dp(10), 0, dp(24)) })
right.addView(label("PUBLIC PREVIEW · ${version()}", 12, MUTED).apply { letterSpacing = .08f })
right.addView(button(getString(R.string.connect_with_usb), false) { connect(false) }, matchButton())
right.addView(label(getString(R.string.plug_your_iphone_into_a_usb_data_port_allow_carplay_when_y), 14, MUTED).apply { gravity = Gravity.CENTER; setPadding(dp(8), dp(10), dp(8), dp(24)) })
right.addView(button(getString(R.string.settings), false) { page = "settings"; render() }, matchButton())
right.addView(label(getString(R.string.make_diplay_feel_right_for_your_car), 14, MUTED).apply { gravity = Gravity.CENTER; setPadding(0, dp(10), 0, dp(24)) })
right.addView(label("${getString(R.string.home_public_preview)}${version()}", 12, MUTED).apply { letterSpacing = .08f })
if (wide) {
// Both rows share column widths. The USB button starts at the wireless
// card's top edge, independently of hero wrapping or font scaling.
@@ -218,50 +218,50 @@ class DiPlayActivity : ComponentActivity() {
}
private fun settings(content: LinearLayout) {
content.addView(label("Your drive, your way.", 34, TEXT, true))
content.addView(label("Apply reconnects CarPlay for size, resolution, music buffer and frame rate. Other changes apply to your next connection.", 17, MUTED).apply { setPadding(0, dp(8), 0, dp(24)) })
content.addView(label(getString(R.string.your_drive_your_way), 34, TEXT, true))
content.addView(label(getString(R.string.apply_reconnects_carplay_for_size_resolution_music_buffer), 17, MUTED).apply { setPadding(0, dp(8), 0, dp(24)) })
languageSettings(content)
section(content, "Connection setup", R.drawable.ic_dp_connection) { card ->
card.addView(label("Choose how to connect, follow the setup steps and save your car hotspot details.", 16, MUTED))
card.addView(button("Open connection setup", false) { page = "connection"; render() }, matchButton(12, 60))
section(content, getString(R.string.connection_setup), R.drawable.ic_dp_connection) { card ->
card.addView(label(getString(R.string.choose_how_to_connect_follow_the_setup_steps_and_save_your), 16, MUTED))
card.addView(button(getString(R.string.open_connection_setup), false) { page = "connection"; render() }, matchButton(12, 60))
}
section(content, "Diagnostics", R.drawable.ic_dp_diagnostics) { card ->
exportButton = button(if (exportInProgress) "Saving report…" else "Save diagnostic report", false) {
section(content, getString(R.string.diagnostics), R.drawable.ic_dp_diagnostics) { card ->
exportButton = button(if (exportInProgress) getString(R.string.saving_report) else getString(R.string.save_diagnostic_report), false) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) exportDiagnostics()
else chooseReportDestination()
}.apply { isEnabled = !exportInProgress }
card.addView(exportButton, matchButton(10, 60))
card.addView(button("Choose save location", false) { chooseReportDestination() }, matchButton(10, 60))
val destination = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) "Reports save to Downloads/DiPlay. " else "Choose where to save your report. "
card.addView(label(destination + "Nothing is sent automatically. Protocol payloads and credentials are excluded.", 14, MUTED).apply { setPadding(0, dp(12), 0, 0) })
card.addView(button(getString(R.string.choose_save_location), false) { chooseReportDestination() }, matchButton(10, 60))
val destination = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) getString(R.string.reports_save_to_downloads_diplay) else getString(R.string.choose_where_to_save_your_report)
card.addView(label(destination + getString(R.string.nothing_is_sent_automatically_protocol_payloads_and_creden), 14, MUTED).apply { setPadding(0, dp(12), 0, 0) })
}
section(content, "Automatic connection", R.drawable.ic_dp_automation) { card ->
toggle(card, "Connect when DiPlay opens", "Use your last connection type and selected iPhone.", DiPlayPreferences.autoConnect(this)) { DiPlayPreferences.saveAutoConnect(this, it) }
toggle(card, "Open after the car starts", "Availability depends on your head unit’s startup settings.", AirPlayPersistence.loadAutoStartOnBoot(this)) { AirPlayPersistence.saveAutoStartOnBoot(this, it) }
card.addView(button("Choose iPhone · ${DiPlayPreferences.phoneName(this)}", false) { choosePhone() }, matchButton(12, 60))
section(content, getString(R.string.automatic_connection), R.drawable.ic_dp_automation) { card ->
toggle(card, getString(R.string.connect_when_diplay_opens), getString(R.string.use_your_last_connection_type_and_selected_iphone), DiPlayPreferences.autoConnect(this)) { DiPlayPreferences.saveAutoConnect(this, it) }
toggle(card, getString(R.string.open_after_the_car_starts), getString(R.string.availability_depends_on_your_head_unit_s_startup_settings), AirPlayPersistence.loadAutoStartOnBoot(this)) { AirPlayPersistence.saveAutoStartOnBoot(this, it) }
card.addView(button("${getString(R.string.choose_iphone_prefix)}${DiPlayPreferences.phoneName(this)}", false) { choosePhone() }, matchButton(12, 60))
}
section(content, "Display and performance", R.drawable.ic_dp_display) { card ->
section(content, getString(R.string.display_and_performance), R.drawable.ic_dp_display) { card ->
carPlaySizeControl(card)
choice(card, "Resolution", listOf("Native", "80% · lighter load", "60% · lightest load"), listOf(10, 8, 6).indexOf(AirPlayPersistence.loadDisplayScaleTenths(this)).coerceAtLeast(0)) { AirPlayPersistence.saveDisplayScaleTenths(this, listOf(10, 8, 6)[it]) }
choice(card, getString(R.string.resolution), listOf(getString(R.string.resolution_native), getString(R.string.s_80_lighter_load), getString(R.string.s_60_lightest_load)), listOf(10, 8, 6).indexOf(AirPlayPersistence.loadDisplayScaleTenths(this)).coerceAtLeast(0)) { AirPlayPersistence.saveDisplayScaleTenths(this, listOf(10, 8, 6)[it]) }
val bufferPresets = com.shilapi.xcertplay.media.MediaAudioBuffer.presets
choice(card, "Music buffer", listOf("300 ms · default", "500 ms", "1000 ms · most stable"),
choice(card, getString(R.string.music_buffer), listOf(getString(R.string.s_300_ms_default), getString(R.string.s_500_ms), getString(R.string.s_1000_ms_most_stable)),
bufferPresets.indexOf(AirPlayPersistence.loadMediaBufferMillis(this)).coerceAtLeast(0)) {
AirPlayPersistence.saveMediaBufferMillis(this, bufferPresets[it])
}
choice(card, "Frame rate", listOf("30 fps · lighter load", "60 fps · smoother motion"), if (AirPlayPersistence.loadFps(this) == 60) 1 else 0) { AirPlayPersistence.saveFps(this, if (it == 1) 60 else 30) }
toggle(card, "Efficient video", "Use HEVC. Leave off for the widest head-unit compatibility.", AirPlayPersistence.loadHevcEnabled(this)) { AirPlayPersistence.saveHevcEnabled(this, it) }
toggle(card, "Right-hand drive", "Place CarPlay’s controls closer to the driver.", AirPlayPersistence.loadRightHandDrive(this)) { AirPlayPersistence.saveRightHandDrive(this, it) }
toggle(card, "Full screen", "Hide the car’s system bars while CarPlay is open.", AirPlayPersistence.loadHideTopBar(this) && AirPlayPersistence.loadHideBottomBar(this)) {
choice(card, getString(R.string.frame_rate), listOf(getString(R.string.s_30_fps_lighter_load), getString(R.string.s_60_fps_smoother_motion)), if (AirPlayPersistence.loadFps(this) == 60) 1 else 0) { AirPlayPersistence.saveFps(this, if (it == 1) 60 else 30) }
toggle(card, getString(R.string.efficient_video), getString(R.string.use_hevc_leave_off_for_the_widest_head_unit_compatibility), AirPlayPersistence.loadHevcEnabled(this)) { AirPlayPersistence.saveHevcEnabled(this, it) }
toggle(card, getString(R.string.right_hand_drive), getString(R.string.place_carplay_s_controls_closer_to_the_driver), AirPlayPersistence.loadRightHandDrive(this)) { AirPlayPersistence.saveRightHandDrive(this, it) }
toggle(card, getString(R.string.full_screen), getString(R.string.hide_the_car_s_system_bars_while_carplay_is_open), AirPlayPersistence.loadHideTopBar(this) && AirPlayPersistence.loadHideBottomBar(this)) {
AirPlayPersistence.saveHideTopBar(this, it); AirPlayPersistence.saveHideBottomBar(this, it)
}
}
section(content, "Audio routing") { card ->
val channelTitle = label("Navigation stream type", 18, TEXT, true)
val channelHint = label("Tap a number to test. 14 = driver speaker on BYD.", 14, MUTED)
section(content, getString(R.string.audio_routing)) { card ->
val channelTitle = label(getString(R.string.navigation_stream_type), 18, TEXT, true)
val channelHint = label(getString(R.string.tap_a_number_to_test_14_driver_speaker_on_byd), 14, MUTED)
val grid = channelSelector()
val saveButton = button("Save", true) {
val saveButton = button(getString(R.string.save), true) {
AirPlayPersistence.saveNavigationStreamType(this, navigationStreamType)
toast("Saved: $navigationStreamType")
toast(getString(R.string.audio_saved_value, navigationStreamType))
}
fun applyChannelEnabled(enabled: Boolean) {
val alpha = if (enabled) 1f else 0.4f
@@ -278,8 +278,8 @@ class DiPlayActivity : ComponentActivity() {
}
val aaosSupported = resources.getBoolean(R.bool.config_advanced_audio_channel_mapping)
if (aaosSupported) {
toggle(card, "Advanced audio channel mapping",
"Use usage / content-type routing instead of stream type",
toggle(card, getString(R.string.advanced_audio_channel_mapping),
getString(R.string.use_usage_content_type_routing_instead_of_stream_type),
AirPlayPersistence.loadAdvancedAudioChannelMapping(this)) {
AirPlayPersistence.saveAdvancedAudioChannelMapping(this, it)
applyChannelEnabled(!it)
@@ -291,59 +291,59 @@ class DiPlayActivity : ComponentActivity() {
card.addView(saveButton, matchButton(12, 56))
if (aaosSupported) applyChannelEnabled(!AirPlayPersistence.loadAdvancedAudioChannelMapping(this))
}
if (com.shilapi.xcertplay.hud.BydOutputSettings.available(this)) section(content, "BYD navigation", R.drawable.ic_dp_navigation) { card ->
toggle(card, "Navigation on HUD and instrument cluster",
"Show phone navigation arrows, distance and street names on supported BYD displays. Vehicle compatibility varies.",
if (com.shilapi.xcertplay.hud.BydOutputSettings.available(this)) section(content, getString(R.string.byd_navigation), R.drawable.ic_dp_navigation) { card ->
toggle(card, getString(R.string.navigation_on_hud_and_instrument_cluster),
getString(R.string.show_phone_navigation_arrows_distance_and_street_names_on),
com.shilapi.xcertplay.hud.BydOutputSettings.enabled(this)) { com.shilapi.xcertplay.hud.BydOutputSettings.setEnabled(this, it) }
if (ClusterMapPresentation.findDisplay(this) != null) {
toggle(card, "CarPlay map on instrument cluster · experimental",
"Shows the iPhone's cluster map on the instrument cluster. Choose Small or Full screen navi in the cluster's steering-wheel menu.",
toggle(card, getString(R.string.carplay_map_on_instrument_cluster_experimental),
getString(R.string.shows_the_iphone_s_cluster_map_on_the_instrument_cluster_c),
AirPlayPersistence.loadClusterMapEnabled(this)) {
AirPlayPersistence.saveClusterMapEnabled(this, it)
reconnectForClusterMap()
}
if (DiLink51ClusterLayout.supported()) {
val automatic = DiLink51ClusterLayout.automatic(this)
toggle(card, "Follow instrument theme and map card",
"Show the side map only when its card is open, and switch to the full map in Map theme. Theme and card changes keep CarPlay connected.", automatic) {
toggle(card, getString(R.string.follow_instrument_theme_and_map_card),
getString(R.string.show_the_side_map_only_when_its_card_is_open_and_switch_to), automatic) {
DiLink51ClusterLayout.saveAutomatic(this, it)
render()
reconnectForClusterMap()
}
val allowed = DiLink51ClusterMonitor.hasAccess(this)
card.addView(label(if (allowed) "Usage Access: enabled"
else "Usage Access: setup needed for automatic mode", 14, if (allowed) MUTED else WARNING))
card.addView(button("Automatic map setup · ADB", false) { showClusterAccessSetup() }, matchButton(10, 56))
card.addView(label(if (allowed) getString(R.string.usage_access_enabled)
else getString(R.string.usage_access_setup_needed_for_automatic_mode), 14, if (allowed) MUTED else WARNING))
card.addView(button(getString(R.string.automatic_map_setup_adb), false) { showClusterAccessSetup() }, matchButton(10, 56))
if (!automatic) {
val themes = DiLink51ClusterLayout.Theme.entries
choice(card, "Instrument theme", themes.map { it.label }, themes.indexOf(DiLink51ClusterLayout.theme(this))) {
choice(card, getString(R.string.instrument_theme), themes.map { it.label }, themes.indexOf(DiLink51ClusterLayout.theme(this))) {
DiLink51ClusterLayout.saveTheme(this, themes[it])
reconnectForClusterMap()
}
card.addView(label("Manual mode: match the cluster theme here. The map cannot follow card visibility without Usage Access.", 14, MUTED))
card.addView(label(getString(R.string.manual_mode_match_the_cluster_theme_here_the_map_cannot_fo), 14, MUTED))
}
val contrasts = DiLink51ClusterLayout.Contrast.entries
choice(card, "Instrument contrast", contrasts.map { it.label }, contrasts.indexOf(DiLink51ClusterLayout.contrast(this))) {
choice(card, getString(R.string.instrument_contrast), contrasts.map { it.label }, contrasts.indexOf(DiLink51ClusterLayout.contrast(this))) {
DiLink51ClusterLayout.saveContrast(this, contrasts[it])
reconnectForClusterMap()
}
} else {
val sizes = CarPlayClusterDisplay.scalePresets
choice(card, "Cluster map size", listOf("Standard · sharpest", "Larger · default", "Largest"),
choice(card, getString(R.string.cluster_map_size), listOf("Standard · sharpest", "Larger · default", "Largest"),
sizes.indexOf(AirPlayPersistence.loadClusterMapScalePercent(this)).coerceAtLeast(0)) {
AirPlayPersistence.saveClusterMapScalePercent(this, sizes[it])
}
val across = CarPlayClusterDisplay.horizontalSteps.toList()
choice(card, "Car marker · horizontal", across.map { markerStepLabel(it, "Left", "Right") },
choice(card, getString(R.string.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") },
choice(card, getString(R.string.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) {
card.addView(button(getString(R.string.reset_car_marker_to_centre), false) {
AirPlayPersistence.saveClusterMarkerHorizontalStep(this, 0)
AirPlayPersistence.saveClusterMarkerVerticalStep(this, 0)
render()
@@ -352,25 +352,25 @@ class DiPlayActivity : ComponentActivity() {
}
}
}
section(content, "Permissions and connection help", R.drawable.ic_dp_permissions) { card ->
card.addView(label("Nearby devices connects your iPhone. Microphone enables Siri and calls. Older Android versions also require Location for wireless setup. USB mode may ask for a local VPN connection.", 16, MUTED))
card.addView(button("App permissions", false) { openSystem(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName"))) }, matchButton(16, 60))
card.addView(button("Bluetooth settings", false) { openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }, matchButton(10, 60))
card.addView(button("Wireless connection help", false) { wirelessHelp() }, matchButton(10, 60))
section(content, getString(R.string.permissions_and_connection_help), R.drawable.ic_dp_permissions) { card ->
card.addView(label(getString(R.string.nearby_devices_connects_your_iphone_microphone_enables_sir), 16, MUTED))
card.addView(button(getString(R.string.app_permissions), false) { openSystem(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName"))) }, matchButton(16, 60))
card.addView(button(getString(R.string.bluetooth_settings), false) { openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }, matchButton(10, 60))
card.addView(button(getString(R.string.wireless_connection_help), false) { wirelessHelp() }, matchButton(10, 60))
}
section(content, "About", R.drawable.ic_dp_about) { card ->
card.addView(button("About DiPlay", false) { page = "about"; render() }, matchButton(0, 60))
section(content, getString(R.string.about), R.drawable.ic_dp_about) { card ->
card.addView(button(getString(R.string.about_diplay), false) { page = "about"; render() }, matchButton(0, 60))
}
}
private fun about(content: LinearLayout) {
content.addView(label("DiPlay", 40, TEXT, true))
content.addView(label("CarPlay, at home in your car.", 20, MUTED).apply { setPadding(0, dp(8), 0, dp(24)) })
section(content, "Public preview · ${version()}") { card ->
card.addView(label("An independent CarPlay receiver for Android head units. Wired and wireless connections run on the head unit, with local authentication. A standard iPhone can connect without a jailbreak, Mac, dongle or sign-in.\n\nThis preview uses an experimental accessory identity. Compatibility with every iPhone and head unit is still being tested. It is not an Apple-certified product.", 17, TEXT))
content.addView(label(getString(R.string.diplay), 40, TEXT, true))
content.addView(label(getString(R.string.carplay_at_home_in_your_car), 20, MUTED).apply { setPadding(0, dp(8), 0, dp(24)) })
section(content, "${getString(R.string.about_public_preview_prefix)}${version()}") { card ->
card.addView(label(getString(R.string.an_independent_carplay_receiver_for_android_head_units_wir), 17, TEXT))
}
section(content, "Made possible by open source") { card ->
card.addView(label("Receiver based on xcertplay, licensed under GPL-3.0. DiPlay’s interface follows DiAuto’s design, licensed under AGPL-3.0.\n\nIncludes AndroidX, Bouncy Castle, JmDNS and SLF4J. Source and license notices accompany this release.\n\nCarPlay and the CarPlay icon belong to Apple Inc. DiPlay is an independent project.", 16, MUTED))
section(content, getString(R.string.made_possible_by_open_source)) { card ->
card.addView(label(getString(R.string.receiver_based_on_xcertplay_licensed_under_gpl_3_0_diplay), 16, MUTED))
}
}
@@ -380,11 +380,11 @@ class DiPlayActivity : ComponentActivity() {
com.shilapi.xcertplay.network.CarHotspotStatus.isEnabled(this) == false
private fun carHotspotOffDialog() {
AlertDialog.Builder(this).setTitle("Car hotspot is off")
.setMessage("DiPlay connects through the car hotspot “${AirPlayPersistence.loadManualHotspotSsid(this)}”. Turn it on in the car settings, then connect.")
.setPositiveButton("Open car settings") { _, _ -> openCarWifiSettings() }
.setNeutralButton("Connect") { _, _ -> connect(true) }
.setNegativeButton("Cancel", null).show()
AlertDialog.Builder(this).setTitle(getString(R.string.car_hotspot_is_off))
.setMessage(getString(R.string.msg_car_hotspot_connect, AirPlayPersistence.loadManualHotspotSsid(this)))
.setPositiveButton(getString(R.string.open_car_settings)) { _, _ -> openCarWifiSettings() }
.setNeutralButton(getString(R.string.connect)) { _, _ -> connect(true) }
.setNegativeButton(getString(R.string.cancel), null).show()
}
// BYD maps the AOSP tether action to its own hotspot screen; other firmware falls back to Wi-Fi settings.
@@ -413,23 +413,23 @@ class DiPlayActivity : ComponentActivity() {
}
private fun connectionSetup(content: LinearLayout) {
content.addView(label("Connection setup", 34, TEXT, true))
content.addView(label("Set up once. Your details stay saved for the next drive. Changes apply to your next connection.", 17, MUTED).apply { setPadding(0, dp(8), 0, dp(24)) })
section(content, "1 · Choose your connection") { card -> wirelessLinkControls(card) }
section(content, "2 · Pair your iPhone") { card ->
card.addView(label("Keep Bluetooth and Wi-Fi on your iPhone. Pair with the car’s Bluetooth, then select your iPhone here. Allow Nearby devices when asked.", 16, MUTED))
card.addView(button("Choose iPhone · ${DiPlayPreferences.phoneName(this)}", false) { choosePhone() }, matchButton(12, 60))
card.addView(button("Review app permissions", false) {
content.addView(label(getString(R.string.connection_setup), 34, TEXT, true))
content.addView(label(getString(R.string.set_up_once_your_details_stay_saved_for_the_next_drive_cha), 17, MUTED).apply { setPadding(0, dp(8), 0, dp(24)) })
section(content, getString(R.string.s_1_choose_your_connection)) { card -> wirelessLinkControls(card) }
section(content, getString(R.string.s_2_pair_your_iphone)) { card ->
card.addView(label(getString(R.string.keep_bluetooth_and_wi_fi_on_your_iphone_pair_with_the_car), 16, MUTED))
card.addView(button("${getString(R.string.choose_iphone_prefix)}${DiPlayPreferences.phoneName(this)}", false) { choosePhone() }, matchButton(12, 60))
card.addView(button(getString(R.string.review_app_permissions), false) {
openSystem(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName")))
}, matchButton(12, 60))
}
section(content, "3 · Connect") { card ->
card.addView(label("Return from car settings to DiPlay, then connect. Accept the CarPlay prompt on your iPhone. A car internet plan is not required; mobile data availability depends on your phone’s network settings.", 16, MUTED))
card.addView(button("Connect phone", true) { connect(true) }, matchButton(12, 60))
section(content, getString(R.string.s_3_connect)) { card ->
card.addView(label(getString(R.string.return_from_car_settings_to_diplay_then_connect_accept_the), 16, MUTED))
card.addView(button(getString(R.string.connect_phone), true) { connect(true) }, matchButton(12, 60))
}
section(content, "Prefer a cable?") { card ->
card.addView(label("Use a USB data cable and the car’s USB data port. Unlock your iPhone and allow CarPlay. No hotspot setup is needed.", 16, MUTED))
card.addView(button("Connect with USB", false) { connect(false) }, matchButton(12, 60))
section(content, getString(R.string.prefer_a_cable)) { card ->
card.addView(label(getString(R.string.use_a_usb_data_cable_and_the_car_s_usb_data_port_unlock_yo), 16, MUTED))
card.addView(button(getString(R.string.connect_with_usb), false) { connect(false) }, matchButton(12, 60))
}
}
@@ -461,20 +461,20 @@ class DiPlayActivity : ComponentActivity() {
option.addView(label(descriptions[index], 15, MUTED).apply { setPadding(0, dp(6), 0, dp(12)) })
}
if (mode == WirelessHotspotMode.MANUAL) {
parent.addView(label("Hotspot setup", 22, TEXT, true))
parent.addView(label("1. Open car hotspot settings, turn the hotspot on and select 5 GHz if available.\n2. Copy its name and password below exactly.\n3. Leave the car hotspot on when connecting. DiPlay sends the details over Bluetooth; your iPhone joins automatically.", 16, MUTED).apply { setPadding(0, dp(8), 0, dp(12)) })
parent.addView(button("Open car hotspot settings", false) { openCarWifiSettings() }, matchButton(0, 60))
parent.addView(button(if (pendingCarHotspotSetup) "Save hotspot details and use this mode" else "Edit saved hotspot · ${storedSsid()}", false) {
parent.addView(label(getString(R.string.hotspot_setup), 22, TEXT, true))
parent.addView(label(getString(R.string.s_1_open_car_hotspot_settings_turn_the_hotspot_on_and_sele), 16, MUTED).apply { setPadding(0, dp(8), 0, dp(12)) })
parent.addView(button(getString(R.string.open_car_hotspot_settings), false) { openCarWifiSettings() }, matchButton(0, 60))
parent.addView(button(if (pendingCarHotspotSetup) getString(R.string.save_hotspot_details_and_use_this_mode) else "${getString(R.string.edit_saved_hotspot_prefix)}${storedSsid()}", false) {
askHotspotCredentials { ssid, password ->
saveHotspotCredentials(ssid, password)
pendingCarHotspotSetup = false
applyWirelessLink(WirelessHotspotMode.MANUAL)
}
}, matchButton(12, 60))
parent.addView(label(if (pendingCarHotspotSetup) "Finish setup · Save your hotspot details to use this mode." else if (carHotspotOff()) "Hotspot is off · Turn it on in car settings." else "Details saved · Check that the car hotspot is on before connecting.", 15, if (carHotspotOff()) WARNING else MUTED).apply { setPadding(0, dp(12), 0, 0) })
parent.addView(label(if (pendingCarHotspotSetup) getString(R.string.finish_setup_save_your_hotspot_details_to_use_this_mode) else if (carHotspotOff()) "Hotspot is off · Turn it on in car settings." else "Details saved · Check that the car hotspot is on before connecting.", 15, if (carHotspotOff()) WARNING else MUTED).apply { setPadding(0, dp(12), 0, 0) })
} else {
parent.addView(label("Turn the car’s Wi-Fi switch on. Allow Location / Nearby devices if requested and enable Location when Android asks. If playback stutters, try the built-in car hotspot at 5 GHz.", 16, MUTED))
parent.addView(button("Open car Wi-Fi settings", false) { openCarClientWifiSettings() }, matchButton(12, 60))
parent.addView(label(getString(R.string.turn_the_car_s_wi_fi_switch_on_allow_location_nearby_devic), 16, MUTED))
parent.addView(button(getString(R.string.open_car_wi_fi_settings), false) { openCarClientWifiSettings() }, matchButton(12, 60))
}
}
@@ -494,10 +494,10 @@ class DiPlayActivity : ComponentActivity() {
private fun askHotspotCredentials(done: (String, String) -> Unit) {
val fields = column().apply { setPadding(dp(24), dp(12), dp(24), dp(12)) }
fields.addView(label("Copy these from the car’s hotspot settings. Use 5 GHz if available. Saving here does not change the car’s hotspot.", 16, MUTED))
val ssid = EditText(this).apply { hint = "Hotspot name"; setText(storedSsid()); setSingleLine() }
fields.addView(label(getString(R.string.copy_these_from_the_car_s_hotspot_settings_use_5_ghz_if_av), 16, MUTED))
val ssid = EditText(this).apply { hint = getString(R.string.hotspot_name); setText(storedSsid()); setSingleLine() }
val password = EditText(this).apply {
hint = "Hotspot password"; setText(storedPassword()); setSingleLine()
hint = getString(R.string.hotspot_password); setText(storedPassword()); setSingleLine()
inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
}
ssid.imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_NEXT or android.view.inputmethod.EditorInfo.IME_FLAG_NO_EXTRACT_UI
@@ -516,7 +516,7 @@ class DiPlayActivity : ComponentActivity() {
}
fields.addView(ssid); fields.addView(password)
fields.addView(CheckBox(this).apply {
text = "Show password"
text = getString(R.string.show_password)
setOnCheckedChangeListener { _, checked ->
password.transformationMethod = if (checked) null else android.text.method.PasswordTransformationMethod.getInstance()
password.setSelection(password.text.length)
@@ -525,10 +525,10 @@ class DiPlayActivity : ComponentActivity() {
val error = label("", 14, WARNING)
error.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
fields.addView(error)
val dialog = AlertDialog.Builder(this).setTitle("Car hotspot details")
val dialog = AlertDialog.Builder(this).setTitle(getString(R.string.car_hotspot_details))
.setView(ScrollView(this).apply { addView(fields) })
.setPositiveButton("Save details", null).setNegativeButton("Cancel") { _, _ -> hideKeyboard() }
.setNeutralButton("Hide keyboard", null).create()
.setPositiveButton(getString(R.string.save_details), null).setNegativeButton(getString(R.string.cancel)) { _, _ -> hideKeyboard() }
.setNeutralButton(getString(R.string.hide_keyboard), null).create()
dialog.setOnShowListener {
dialog.window?.setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
dialog.getButton(android.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener { hideKeyboard() }
@@ -553,27 +553,27 @@ class DiPlayActivity : ComponentActivity() {
private fun showClusterAccessSetup() {
val command = "adb shell appops set $packageName GET_USAGE_STATS allow"
val body = column().apply { setPadding(dp(24), dp(12), dp(24), dp(12)) }
body.addView(label("One-time setup on this car", 20, TEXT, true))
body.addView(label("Usage Access lets DiPlay follow the instrument theme and map card. Only BYD cluster activity events are processed locally. This car does not provide a working permission settings page.", 15, MUTED))
body.addView(label("1. Connect a computer with ADB installed to the car using your existing USB or wireless debugging connection. Approve debugging on the car if asked.\n\n2. Run this command in the computer’s terminal:", 16, TEXT))
body.addView(label(getString(R.string.one_time_setup_on_this_car), 20, TEXT, true))
body.addView(label(getString(R.string.usage_access_lets_diplay_follow_the_instrument_theme_and_m), 15, MUTED))
body.addView(label(getString(R.string.s_1_connect_a_computer_with_adb_installed_to_the_car_using), 16, TEXT))
body.addView(label(command, 16, TEXT).apply {
typeface = android.graphics.Typeface.MONOSPACE
setTextIsSelectable(true)
setPadding(0, dp(16), 0, dp(16))
})
body.addView(button("Copy command", false) {
body.addView(button(getString(R.string.copy_command), false) {
getSystemService(android.content.ClipboardManager::class.java).setPrimaryClip(
android.content.ClipData.newPlainText("DiPlay Usage Access", command))
toast("Copied to the car clipboard. Run the command on your computer.")
toast(getString(R.string.copied_to_the_car_clipboard_run_the_command_on_your_comput))
}, matchButton(0, 56))
body.addView(label("If ADB lists several devices, use adb -s <car-device-ID> shell appops set $packageName GET_USAGE_STATS allow. Find the ID with adb devices.", 14, MUTED))
body.addView(label("3. Tap Check and enable below. This enables the cluster map and automatic theme following. An active CarPlay session reconnects once. You can then disconnect the computer.", 16, TEXT))
body.addView(label(getString(R.string.cluster_adb_multi_device, packageName), 14, MUTED))
body.addView(label(getString(R.string.s_3_tap_check_and_enable_below_this_enables_the_cluster_ma), 16, TEXT))
val status = label(if (DiLink51ClusterMonitor.hasAccess(this)) "Permission enabled · ready to use" else "Permission not enabled yet", 16, TEXT)
body.addView(status)
val dialog = AlertDialog.Builder(this).setTitle("Automatic cluster map setup")
val dialog = AlertDialog.Builder(this).setTitle(getString(R.string.automatic_cluster_map_setup))
.setView(ScrollView(this).apply { addView(body) })
.setNegativeButton("Close", null)
.setPositiveButton("Check and enable", null).create()
.setNegativeButton(getString(R.string.close), null)
.setPositiveButton(getString(R.string.check_and_enable), null).create()
dialog.setOnShowListener {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (DiLink51ClusterMonitor.hasAccess(this)) {
@@ -581,10 +581,10 @@ class DiPlayActivity : ComponentActivity() {
DiLink51ClusterLayout.saveAutomatic(this, true)
dialog.dismiss()
render()
toast("Automatic map enabled. Open the cluster map card or select Map theme.")
toast(getString(R.string.automatic_map_enabled_open_the_cluster_map_card_or_select))
reconnectForClusterMap()
} else {
status.text = "Still waiting for Usage Access. Check that the command ran successfully for this car, then try again."
status.text = getString(R.string.still_waiting_for_usage_access_check_that_the_command_ran)
}
}
}
@@ -592,7 +592,7 @@ class DiPlayActivity : ComponentActivity() {
}
// 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.
// its current link. The position choices need no call: getString(R.string.apply_and_reconnect) already does it.
private fun reconnectForClusterMap() {
if (CarPlayBackgroundSession.hasSession()) connect(AirPlayPersistence.loadWirelessEnabled(this))
}
@@ -600,7 +600,7 @@ class DiPlayActivity : ComponentActivity() {
private fun applyWirelessLink(mode: WirelessHotspotMode) {
AirPlayPersistence.saveWirelessHotspotMode(this, mode)
render()
toast("Saved for your next connection")
toast(getString(R.string.saved_for_your_next_connection))
}
private fun textInput(title: String, current: String, secret: Boolean, save: (String) -> Unit) {
@@ -614,30 +614,30 @@ class DiPlayActivity : ComponentActivity() {
}
}
AlertDialog.Builder(this).setTitle(title).setView(input)
.setPositiveButton("Save") { _, _ -> save(input.text.toString().let { if (secret) it else it.trim() }) }
.setNegativeButton("Cancel", null).show()
.setPositiveButton(getString(R.string.save)) { _, _ -> save(input.text.toString().let { if (secret) it else it.trim() }) }
.setNegativeButton(getString(R.string.cancel), null).show()
}
private fun carPlaySizeControl(parent: LinearLayout) {
val sizes = com.shilapi.xcertplay.airplay.CarPlaySize.entries
val current = com.shilapi.xcertplay.airplay.CarPlaySize.fromWidthMillimeters(AirPlayPersistence.loadWidthPhysicalMm(this))
choice(parent, "CarPlay size", sizes.map { it.label }, sizes.indexOf(current)) {
choice(parent, getString(R.string.carplay_size), sizes.map { it.label }, sizes.indexOf(current)) {
AirPlayPersistence.saveWidthPhysicalMm(this, sizes[it].widthMillimeters)
}
parent.addView(label("Changes the size of CarPlay icons and text. Applying a size reconnects CarPlay.", 14, MUTED).apply {
parent.addView(label(getString(R.string.changes_the_size_of_carplay_icons_and_text_applying_a_size), 14, MUTED).apply {
setPadding(0, 0, 0, dp(18))
})
}
private fun connect(wireless: Boolean) {
if (wireless && pendingCarHotspotSetup) { toast("Save your hotspot details in Connection setup first"); page = "connection"; render(); return }
if (wireless && pendingCarHotspotSetup) { toast(getString(R.string.save_your_hotspot_details_in_connection_setup_first)); page = "connection"; render(); return }
if (setupError != null) { toast(setupError!!); return }
if (wireless && AirPlayPersistence.loadWirelessHotspotMode(this) == WirelessHotspotMode.MANUAL &&
hotspotError(storedSsid(), storedPassword()) != null) {
pendingCarHotspotSetup = true
page = "connection"
render()
toast("Save the name and password from the car’s hotspot settings first")
toast(getString(R.string.save_the_name_and_password_from_the_car_s_hotspot_settings))
return
}
if (wireless && carHotspotOff()) { carHotspotOffDialog(); return }
@@ -667,21 +667,21 @@ class DiPlayActivity : ComponentActivity() {
}
val adapter = getSystemService(BluetoothManager::class.java)?.adapter
if (adapter == null || !adapter.isEnabled) {
AlertDialog.Builder(this).setTitle("Turn on Bluetooth")
.setMessage("Enable the car’s Bluetooth and pair your iPhone first.")
.setPositiveButton("Open Bluetooth") { _, _ -> openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }
.setNegativeButton("Later", null).show(); return
AlertDialog.Builder(this).setTitle(getString(R.string.turn_on_bluetooth))
.setMessage(getString(R.string.enable_the_car_s_bluetooth_and_pair_your_iphone_first))
.setPositiveButton(getString(R.string.open_bluetooth)) { _, _ -> openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }
.setNegativeButton(getString(R.string.later), null).show(); return
}
val devices = runCatching { adapter.bondedDevices.sortedBy { it.name ?: "" } }.getOrDefault(emptyList())
if (devices.isEmpty()) {
AlertDialog.Builder(this).setTitle("Pair your iPhone")
.setMessage("On your iPhone, open Settings → Bluetooth and pair with the car. Then return to DiPlay and choose Connect phone.")
.setPositiveButton("Open Bluetooth") { _, _ -> openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }
.setNegativeButton("Got it", null).show(); return
AlertDialog.Builder(this).setTitle(getString(R.string.pair_your_iphone))
.setMessage(getString(R.string.on_your_iphone_open_settings_bluetooth_and_pair_with_the_c))
.setPositiveButton(getString(R.string.open_bluetooth)) { _, _ -> openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }
.setNegativeButton(getString(R.string.got_it), null).show(); return
}
AlertDialog.Builder(this).setTitle("Choose your iPhone")
AlertDialog.Builder(this).setTitle(getString(R.string.choose_your_iphone))
.setItems(devices.map { device ->
val name = device.name ?: "Paired device"
val name = device.name ?: getString(R.string.paired_device)
if (devices.count { it.name == device.name } > 1) "$name · ${device.address.takeLast(5)}" else name
}.toTypedArray()) { _, index ->
val device = devices[index]
@@ -689,15 +689,15 @@ class DiPlayActivity : ComponentActivity() {
val start = pendingWireless; pendingWireless = false
render()
if (start) connect(true)
}.setNeutralButton("Pair another") { _, _ -> openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }
.setNegativeButton("Cancel") { _, _ -> pendingWireless = false }.show()
}.setNeutralButton(getString(R.string.pair_another)) { _, _ -> openSystem(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) }
.setNegativeButton(getString(R.string.cancel)) { _, _ -> pendingWireless = false }.show()
}
private fun wirelessHelp() {
AlertDialog.Builder(this).setTitle("Wireless connection help")
.setMessage("Pair your iPhone with the car’s Bluetooth, keep Wi-Fi on, and allow CarPlay on the iPhone. Close any other phone-projection app.\n\nIf a previous projection app left its connection running, reset CarPlay Wi-Fi below and connect again. Your car’s normal internet Wi-Fi stays on.")
.setPositiveButton("Got it", null)
.setNeutralButton("Reset CarPlay Wi-Fi") { _, _ ->
AlertDialog.Builder(this).setTitle(getString(R.string.wireless_connection_help))
.setMessage(getString(R.string.pair_your_iphone_with_the_car_s_bluetooth_keep_wi_fi_on_an))
.setPositiveButton(getString(R.string.got_it), null)
.setNeutralButton(getString(R.string.reset_carplay_wi_fi)) { _, _ ->
confirmWirelessReset()
}.show()
}
@@ -709,16 +709,16 @@ class DiPlayActivity : ComponentActivity() {
}
private fun confirmWirelessReset() {
AlertDialog.Builder(this).setTitle("Reset CarPlay Wi-Fi?")
.setMessage("This ends the existing Wi-Fi Direct connection, including one left behind after reinstalling. Close other projection apps first. Your car’s internet Wi-Fi stays on.")
.setPositiveButton("Reset and connect") { _, _ ->
AlertDialog.Builder(this).setTitle(getString(R.string.reset_carplay_wi_fi_2))
.setMessage(getString(R.string.this_ends_the_existing_wi_fi_direct_connection_including_o))
.setPositiveButton(getString(R.string.reset_and_connect)) { _, _ ->
CarPlayBackgroundSession.stop { runOnUiThread { resetWirelessGroup() } }
}.setNegativeButton("Cancel", null).show()
}.setNegativeButton(getString(R.string.cancel), null).show()
}
private fun resetWirelessGroup() {
val manager = getSystemService(android.net.wifi.p2p.WifiP2pManager::class.java)
if (manager == null) { toast("This head unit does not support Wi-Fi Direct."); return }
if (manager == null) { toast(getString(R.string.this_head_unit_does_not_support_wi_fi_direct)); return }
val channel = manager.initialize(this, mainLooper, null)
try {
manager.requestGroupInfo(channel) { group ->
@@ -731,7 +731,7 @@ class DiPlayActivity : ComponentActivity() {
when {
remaining == null -> { channel.close(); if (!isFinishing && !isDestroyed) connect(true) }
android.os.SystemClock.elapsedRealtime() >= deadline -> {
channel.close(); toast("Wi-Fi Direct is still busy. Close the other projection app and try again.")
channel.close(); toast(getString(R.string.wi_fi_direct_is_still_busy_close_the_other_projection_app))
}
else -> handler.postDelayed({ waitUntilRemoved() }, 200)
}
@@ -739,25 +739,25 @@ class DiPlayActivity : ComponentActivity() {
}
waitUntilRemoved()
}
override fun onFailure(reason: Int) { channel.close(); toast("Could not reset Wi-Fi Direct. Close the other projection app and try again.") }
override fun onFailure(reason: Int) { channel.close(); toast(getString(R.string.could_not_reset_wi_fi_direct_close_the_other_projection_ap)) }
})
}
} catch (_: SecurityException) {
channel.close(); permissionHelp("Wireless permissions", "Allow Nearby devices and, on older Android versions, Location before resetting CarPlay Wi-Fi.")
channel.close(); permissionHelp(getString(R.string.wireless_permissions), getString(R.string.allow_nearby_devices_and_on_older_android_versions_locatio))
}
}
private fun refreshStatus() {
val running = CarPlayBackgroundSession.hasSession()
status?.text = when {
setupError != null -> "Setup needs attention"
CarPlayBackgroundSession.active -> "CarPlay connected"
running -> "Connecting to your iPhone…"
DiPlayPreferences.phoneAddress(this) != null -> "Ready for ${DiPlayPreferences.phoneName(this)}"
else -> "Ready when you are"
setupError != null -> getString(R.string.setup_needs_attention)
CarPlayBackgroundSession.active -> getString(R.string.carplay_connected)
running -> getString(R.string.connecting_to_your_iphone)
DiPlayPreferences.phoneAddress(this) != null -> "${getString(R.string.status_ready_for_prefix)}${DiPlayPreferences.phoneName(this)}"
else -> getString(R.string.ready_when_you_are)
}
if (lastRunning != running) {
connectButton?.text = if (running) "Open CarPlay" else "Connect phone"
connectButton?.text = if (running) getString(R.string.open_carplay) else getString(R.string.connect_phone)
disconnectButton?.visibility = if (running) View.VISIBLE else View.GONE
disconnectButton?.isEnabled = true
lastRunning = running
@@ -771,15 +771,15 @@ class DiPlayActivity : ComponentActivity() {
// the result callback and the background writer's exception handler ever run.
runCatching { export.launch(reportFileName()) }.onFailure {
toast(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
"This head unit could not open a save location. Please try saving to Downloads again."
else "This head unit has no available file picker to save the report.")
getString(R.string.this_head_unit_could_not_open_a_save_location_please_try_s)
else getString(R.string.this_head_unit_has_no_available_file_picker_to_save_the_re))
}
}
private fun exportDiagnostics(uri: Uri? = null) {
if (exportInProgress) return
exportInProgress = true
exportButton?.apply { isEnabled = false; text = "Saving report…" }
exportButton?.apply { isEnabled = false; text = getString(R.string.saving_report) }
val appContext = applicationContext
val fileName = reportFileName()
Thread({
@@ -816,36 +816,36 @@ class DiPlayActivity : ComponentActivity() {
runOnUiThread {
exportInProgress = false
if (isFinishing || isDestroyed) return@runOnUiThread
exportButton?.apply { isEnabled = true; text = "Save diagnostic report" }
exportButton?.apply { isEnabled = true; text = getString(R.string.save_diagnostic_report) }
if (result.isSuccess) {
val savedUri = result.getOrThrow()
AlertDialog.Builder(this).setTitle("Diagnostic report saved")
.setMessage(if (uri == null) "Downloads/DiPlay/$fileName" else "Your report was saved to the selected location.")
.setPositiveButton("Done", null)
.setNeutralButton("Share") { _, _ ->
AlertDialog.Builder(this).setTitle(getString(R.string.diagnostic_report_saved))
.setMessage(if (uri == null) "Downloads/DiPlay/$fileName" else getString(R.string.your_report_was_saved_to_the_selected_location))
.setPositiveButton(getString(R.string.done), null)
.setNeutralButton(getString(R.string.share)) { _, _ ->
runCatching {
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
type = "text/plain"; putExtra(Intent.EXTRA_STREAM, savedUri)
clipData = android.content.ClipData.newRawUri("Diagnostic report", savedUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, "Share diagnostic report"))
}.onFailure { toast("Report saved. Open it from your file manager to share it.") }
}.onFailure { toast(getString(R.string.report_saved_open_it_from_your_file_manager_to_share_it)) }
}.show()
} else {
AlertDialog.Builder(this).setTitle("Could not save the report")
.setMessage("Check that storage is available, or choose another save location.")
.setPositiveButton("Choose location") { _, _ -> chooseReportDestination() }
.setNegativeButton("Close", null).show()
AlertDialog.Builder(this).setTitle(getString(R.string.could_not_save_the_report))
.setMessage(getString(R.string.check_that_storage_is_available_or_choose_another_save_loc))
.setPositiveButton(getString(R.string.choose_location)) { _, _ -> chooseReportDestination() }
.setNegativeButton(getString(R.string.close), null).show()
}
}
}, "diplay-export").start()
}
private fun permissionHelp(title: String, body: String) {
AlertDialog.Builder(this).setTitle(title).setMessage(body).setPositiveButton("App settings") { _, _ ->
AlertDialog.Builder(this).setTitle(title).setMessage(body).setPositiveButton(getString(R.string.app_settings)) { _, _ ->
openSystem(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName")))
}.setNegativeButton("Later", null).show()
}.setNegativeButton(getString(R.string.later), null).show()
}
private fun openSystem(intent: Intent) { runCatching { startActivity(intent) }.onFailure { toast("Open this setting from your car’s Settings app.") } }
private fun openSystem(intent: Intent) { runCatching { startActivity(intent) }.onFailure { toast(getString(R.string.open_this_setting_from_your_car_s_settings_app)) } }
private fun toast(message: String) { Toast.makeText(this, message, Toast.LENGTH_LONG).show() }
private fun playTestTone(streamType: Int) {
@@ -865,7 +865,7 @@ class DiPlayActivity : ComponentActivity() {
if (track.state != AudioTrack.STATE_INITIALIZED) {
Log.w("DiPlay", "playTestTone streamType=$streamType state=${track.state}")
track.release()
toast("Stream type $streamType is unavailable here (state=${track.state})")
toast(getString(R.string.audio_stream_unavailable, streamType, track.state))
return
}
track.write(pcm, 0, pcm.size)
@@ -982,7 +982,7 @@ class DiPlayActivity : ComponentActivity() {
var pendingSelection = selection
AlertDialog.Builder(this).setTitle(title)
.setSingleChoiceItems(options.toTypedArray(), selection) { _, index -> pendingSelection = index }
.setPositiveButton(if (CarPlayBackgroundSession.hasSession()) "Apply and reconnect" else "Save") { _, _ ->
.setPositiveButton(if (CarPlayBackgroundSession.hasSession()) getString(R.string.apply_and_reconnect) else getString(R.string.save)) { _, _ ->
if (pendingSelection != selection) {
selection = pendingSelection
save(selection)
@@ -991,7 +991,7 @@ class DiPlayActivity : ComponentActivity() {
connect(AirPlayPersistence.loadWirelessEnabled(this))
}
}
}.setNegativeButton("Cancel", null).show()
}.setNegativeButton(getString(R.string.cancel), null).show()
}
parent.addView(button, matchButton(0, 60)); parent.addView(space(12))
}
@@ -20,6 +20,7 @@ import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import com.shilapi.xcertplay.host.R
import java.io.ByteArrayOutputStream
import java.util.concurrent.Executors
import kotlin.math.ceil
@@ -51,7 +52,7 @@ class ImageCropActivity : Activity() {
textSize = 16f
gravity = Gravity.CENTER
setPadding(dp(12), dp(8), dp(12), dp(8))
text = "Loading image"
text = getString(R.string.loading_image)
}
val controls = LinearLayout(this).apply {
@@ -61,7 +62,7 @@ class ImageCropActivity : Activity() {
}
controls.addView(
Button(this).apply {
text = "Cancel"
text = getString(R.string.cancel)
isAllCaps = false
setOnClickListener {
setResult(RESULT_CANCELED)
@@ -72,7 +73,7 @@ class ImageCropActivity : Activity() {
)
controls.addView(
Button(this).apply {
text = "Save 1:1"
text = getString(R.string.save_1_1)
isAllCaps = false
setOnClickListener { saveCrop() }
},
@@ -121,10 +122,10 @@ class ImageCropActivity : Activity() {
return@runOnUiThread
}
if (bitmap == null) {
statusView.text = "Could not decode image"
statusView.text = getString(R.string.could_not_decode_image)
} else {
cropView.setBitmap(bitmap)
statusView.text = "Drag to move, pinch to zoom"
statusView.text = getString(R.string.drag_to_move_pinch_to_zoom)
}
}
}
@@ -138,13 +139,13 @@ class ImageCropActivity : Activity() {
private fun saveCrop() {
val cropped = cropView.cropToSquare() ?: run {
statusView.text = "Image is not ready"
statusView.text = getString(R.string.image_is_not_ready)
return
}
val encoded = ByteArrayOutputStream().use { output ->
if (!cropped.compress(Bitmap.CompressFormat.PNG, 100, output)) {
cropped.recycle()
statusView.text = "Could not encode image"
statusView.text = getString(R.string.could_not_encode_image)
return
}
cropped.recycle()
@@ -158,7 +159,7 @@ class ImageCropActivity : Activity() {
)
finish()
} catch (_: Exception) {
statusView.text = "Could not save image"
statusView.text = getString(R.string.could_not_save_image)
}
}
@@ -0,0 +1,340 @@
<resources>
<string name="msg_car_hotspot_off">车机热点“%1$s”未开启。请在车机设置中开启后再连接。</string>
<string name="home_public_preview">公开预览 ·\u0020\u0020</string>
<string name="choose_iphone_prefix">选择 iPhone ·\u0020</string>
<string name="audio_saved_value">已保存:%1$d</string>
<string name="about_public_preview_prefix">公开预览 ·\u0020</string>
<string name="msg_car_hotspot_connect">DiPlay 通过车机热点“%1$s”连接。请在车机设置中开启热点后连接。</string>
<string name="edit_saved_hotspot_prefix">编辑已保存的热点 ·\u0020</string>
<string name="cluster_adb_multi_device">如果 ADB 列出多个设备,请使用 adb -s &lt;car-device-ID&gt; shell appops set %1$s GET_USAGE_STATS allow。用 adb devices 查询 ID。</string>
<string name="status_ready_for_prefix">已准备连接:</string>
<string name="audio_stream_unavailable">流类型 %1$d 在此不可用(状态=%2$s)</string>
<string name="resolution_handshake_prefix">握手分辨率:</string>
<string name="safe_area_full_screen_at">安全区域:全屏,</string>
<string name="safe_area_prefix">安全区域:</string>
<string name="settings_android9_compat">以下设置在 Android 9(API 28)上不可用且已隐藏:\n• Wi-Fi P2P(5 GHz)— 将改用 LocalOnlyHotspot。\n• HEVC 软解码器 — 将改用硬件解码。</string>
<string name="settings_mfi_address_hint">地址必须以 http:// 或 https:// 开头。令牌为可选项,将作为 Authorization bearer token 发送。</string>
<string name="a_familiar_drive">熟悉的驾乘体验。</string>
<string name="a_previous_wi_fi_direct_connection_is_still_running_reset">之前的 Wi-Fi Direct 连接仍在运行。请重置后再连接。</string>
<string name="about">关于</string>
<string name="about_diplay">关于 DiPlay</string>
<string name="active">已启用</string>
<string name="advanced_audio_channel_mapping">高级音频通道映射</string>
<string name="airplay_icon">AirPlay 图标</string>
<string name="allow_carplay_ui_outside_the_safe_area">允许 CarPlay 界面超出安全区域</string>
<string name="allow_nearby_devices_and_on_older_android_versions_locatio">重置 CarPlay Wi-Fi 前,请授予“附近的设备”权限;较旧的 Android 版本还需要位置权限。</string>
<string name="allow_nearby_devices_for_diplay_in_the_head_unit_s_app_per">请在车机应用权限中允许 DiPlay 访问附近的设备。</string>
<string name="allow_nearby_devices_so_diplay_can_connect_to_your_paired">请允许 DiPlay 访问附近的设备,以连接已配对的 iPhone。</string>
<string name="allow_precise_location_for_diplay_in_the_head_unit_s_app_p">请在车机应用权限中允许 DiPlay 使用精确位置信息。</string>
<string name="allow_the_connection_permission_to_continue">请授予连接权限以继续</string>
<string name="an_independent_carplay_receiver_for_android_head_units_wir">面向 Android 车机的独立 CarPlay 接收器。有线和无线连接均在车机上运行,并使用本地认证。普通 iPhone 无需越狱、Mac、转接器或登录即可连接。\n\n此预览版使用实验性配件身份,仍在测试与不同 iPhone 和车机的兼容性。本产品未经 Apple 认证。</string>
<string name="android_9_compatibility">Android 9 兼容性</string>
<string name="app_name">DiPlay</string>
<string name="app_permissions">应用权限</string>
<string name="app_settings">应用设置</string>
<string name="apply_and_reconnect">应用并重新连接</string>
<string name="apply_reconnects_carplay_for_size_resolution_music_buffer">应用尺寸、分辨率、音乐缓冲和帧率设置时会重新连接 CarPlay。其他改动将在下次连接时生效。</string>
<string name="audio">音频</string>
<string name="audio_routing">音频路由</string>
<string name="auto">自动</string>
<string name="auto_start_on_boot">开机后自动启动</string>
<string name="automatic_cluster_map_setup">自动仪表盘地图设置</string>
<string name="automatic_connection">自动连接</string>
<string name="automatic_map_enabled_open_the_cluster_map_card_or_select">自动地图已启用。打开仪表盘地图卡片或选择“地图主题”。</string>
<string name="automatic_map_setup_adb">自动地图设置 · ADB</string>
<string name="availability_depends_on_your_head_unit_s_startup_settings">是否可用取决于车机的启动设置。</string>
<string name="back">返回</string>
<string name="back_to_diplay">返回 DiPlay</string>
<string name="band">频段</string>
<string name="bluetooth_settings">蓝牙设置</string>
<string name="byd_navigation">比亚迪导航</string>
<string name="cancel">取消</string>
<string name="car_home">车机主页</string>
<string name="car_hotspot_details">车机热点详情</string>
<string name="car_hotspot_is_off">车机热点未开启</string>
<string name="car_marker_horizontal">车辆标记 · 水平</string>
<string name="car_marker_vertical">车辆标记 · 垂直</string>
<string name="carplay">CarPlay</string>
<string name="carplay_at_home_in_your_car">让 CarPlay 融入车机。</string>
<string name="carplay_connected">CarPlay 已连接</string>
<string name="carplay_control_running">CarPlay 控制通道运行中</string>
<string name="carplay_control_window_ended">CarPlay 控制会话已结束</string>
<string name="carplay_icon">CarPlay 图标</string>
<string name="carplay_map_on_instrument_cluster_experimental">仪表盘显示 CarPlay 地图 · 实验性</string>
<string name="carplay_service_already_running">CarPlay 服务已在运行</string>
<string name="carplay_session_already_running">CarPlay 会话已在运行</string>
<string name="carplay_session_ended_reconnecting">CarPlay 会话已结束;正在重新连接</string>
<string name="carplay_settings">CarPlay 设置</string>
<string name="carplay_size">CarPlay 尺寸</string>
<string name="changes_the_size_of_carplay_icons_and_text_applying_a_size">更改 CarPlay 图标和文字的大小。应用尺寸后会重新连接 CarPlay。</string>
<string name="channel_0_auto">信道(0 = 自动)</string>
<string name="channel_is_not_valid_for_the_selected_band">所选频段不支持此信道</string>
<string name="channel_must_be_0_or_1_196">信道必须为 0 或 1–196</string>
<string name="check_and_enable">检查并启用</string>
<string name="check_that_storage_is_available_or_choose_another_save_loc">请检查存储空间,或选择其他保存位置。</string>
<string name="choose_how_to_connect_follow_the_setup_steps_and_save_your">选择连接方式,按步骤操作并保存车机热点信息。</string>
<string name="choose_image">选择图片</string>
<string name="choose_iphone">选择 iPhone</string>
<string name="choose_location">选择位置</string>
<string name="choose_save_location">选择保存位置</string>
<string name="choose_where_to_save_your_report">选择报告的保存位置。</string>
<string name="choose_your_iphone">选择 iPhone</string>
<string name="close">关闭</string>
<string name="cluster_map_size">仪表盘地图尺寸</string>
<string name="common_cancel">取消</string>
<string name="connect">连接</string>
<string name="connect_phone">连接手机</string>
<string name="connect_when_diplay_opens">打开 DiPlay 时连接</string>
<string name="connect_with_usb">通过 USB 连接</string>
<string name="connect_your_iphone_with_a_usb_cable">请使用 USB 数据线连接 iPhone</string>
<string name="connecting_bluetooth">正在连接蓝牙</string>
<string name="connecting_iap2_control">正在连接 iAP2 控制通道</string>
<string name="connecting_to_your_iphone">正在连接 iPhone…</string>
<string name="connection">连接</string>
<string name="connection_interrupted_retrying">连接中断,正在重试…</string>
<string name="connection_setup">连接设置</string>
<string name="copied_to_the_car_clipboard_run_the_command_on_your_comput">已复制到车机剪贴板。在电脑上运行该命令。</string>
<string name="copy_command">复制命令</string>
<string name="copy_these_from_the_car_s_hotspot_settings_use_5_ghz_if_av">从车机热点设置中复制以下内容。如有 5 GHz 请选择。在此保存不会更改车机的热点。</string>
<string name="could_not_decode_image">无法读取图片</string>
<string name="could_not_encode_image">无法编码图片</string>
<string name="could_not_reset_wi_fi_direct_close_the_other_projection_ap">无法重置 Wi-Fi Direct。请关闭其他投屏应用后重试。</string>
<string name="could_not_save_image">无法保存图片</string>
<string name="could_not_save_the_report">无法保存报告</string>
<string name="could_not_start_carplay_return_to_diplay_and_check_app_per">无法启动 CarPlay。请返回 DiPlay 检查应用权限。</string>
<string name="custom_1_1_icon">自定义 1:1 图标</string>
<string name="debug_logs">调试日志</string>
<string name="default_icon">默认图标</string>
<string name="default_placeholder_icon">默认占位图标</string>
<string name="diagnostic_report_saved">诊断报告已保存</string>
<string name="diagnostics">诊断</string>
<string name="diplay">DiPlay</string>
<string name="discard_changes_and_exit_settings">放弃更改并退出设置</string>
<string name="disconnect">断开连接</string>
<string name="discovering_iphone">正在查找 iPhone</string>
<string name="display_and_performance">显示与性能</string>
<string name="display_video">显示与视频</string>
<string name="done">完成</string>
<string name="drag_to_move_pinch_to_zoom">拖动以移动,双指缩放</string>
<string name="draw_outside_safe_area">允许绘制到安全区域外</string>
<string name="driving_side">驾驶方向</string>
<string name="efficient_video">高效视频</string>
<string name="enable_the_car_s_bluetooth_and_pair_your_iphone_first">请先打开车机蓝牙并配对 iPhone。</string>
<string name="error">错误</string>
<string name="exit_application">退出应用</string>
<string name="finish_setup_save_your_hotspot_details_to_use_this_mode">完成设置 · 保存热点信息以使用此模式。</string>
<string name="follow_instrument_theme_and_map_card">跟随仪表盘主题和地图卡片</string>
<string name="frame_rate">帧率</string>
<string name="full_screen">全屏显示</string>
<string name="fullscreen">全屏</string>
<string name="getting_carplay_ready">正在准备 CarPlay…</string>
<string name="got_it">知道了</string>
<string name="handshake_resolution_waiting_for_display">握手分辨率:正在等待显示屏</string>
<string name="hevc_h_265_video_transport">HEVC H.265 视频传输</string>
<string name="hevc_software_decoder">HEVC 软件解码器</string>
<string name="hide_bottom_bar">隐藏底部导航栏</string>
<string name="hide_keyboard">隐藏键盘</string>
<string name="hide_the_car_s_system_bars_while_carplay_is_open">打开 CarPlay 时隐藏车机系统栏。</string>
<string name="hide_the_navigation_bar">隐藏导航栏</string>
<string name="hide_the_status_bar">隐藏状态栏</string>
<string name="hide_top_bar">隐藏顶部状态栏</string>
<string name="hotspot_name">热点名称</string>
<string name="hotspot_password">热点密码</string>
<string name="hotspot_password_contains_u_0000">热点密码包含 U+0000</string>
<string name="hotspot_setup">热点设置</string>
<string name="hotspot_ssid">热点 SSID</string>
<string name="hotspot_ssid_contains_u_0000">热点 SSID 包含 U+0000</string>
<string name="hotspot_ssid_is_required">必须填写热点 SSID</string>
<string name="hotspot_ssid_must_be_at_most_32_utf_8_bytes">热点 SSID 最多为 32 个 UTF-8 字节</string>
<string name="hotspot_status">热点状态</string>
<string name="i2c">I2C</string>
<string name="i2c_device">I2C 设备</string>
<string name="i2c_device_path_contains_u_0000">I2C 设备路径包含 U+0000</string>
<string name="i2c_device_path_is_required">必须填写 I2C 设备路径</string>
<string name="identity_appearance">身份与外观</string>
<string name="image_is_not_ready">图片尚未准备好</string>
<string name="in_carplay_swipe_down_with_three_fingers_to_open_diplay_se">在 CarPlay 界面用三指向下滑动,可打开 DiPlay 设置。</string>
<string name="instrument_contrast">仪表盘对比度</string>
<string name="instrument_theme">仪表盘主题</string>
<string name="keep_bluetooth_and_wi_fi_on_your_iphone_pair_with_the_car">保持 iPhone 的蓝牙和 Wi-Fi 开启。先与车机蓝牙配对,然后在此选择你的 iPhone。出现提示时允许“附近的设备”。</string>
<string name="keep_your_iphone_nearby_with_bluetooth_and_wi_fi_on_allow">请将 iPhone 放在附近并开启蓝牙和 Wi-Fi。\niPhone 出现提示时允许使用 CarPlay。</string>
<string name="language_app_language">应用语言</string>
<string name="language_apply">应用</string>
<string name="language_hint">选择 DiPlay 的语言。“跟随系统”将使用设备语言。</string>
<string name="language_section_title">语言</string>
<string name="language_system_default">跟随系统</string>
<string name="later">稍后</string>
<string name="left_hand_drive">左舵</string>
<string name="linux_device_path_for_example_dev_i2c_1">Linux 设备路径,例如 /dev/i2c-1。</string>
<string name="loading_image">正在加载图片</string>
<string name="local_offline">本地离线</string>
<string name="localonlyhotspot">LocalOnlyHotspot</string>
<string name="location">位置</string>
<string name="longest_height">较长边</string>
<string name="looking_for_your_paired_iphone">正在查找已配对的 iPhone…</string>
<string name="made_possible_by_open_source">开源项目鸣谢</string>
<string name="make_diplay_feel_right_for_your_car">按需调整 DiPlay 设置。</string>
<string name="manual_hotspot">手动热点</string>
<string name="manual_mode_match_the_cluster_theme_here_the_map_cannot_fo">手动模式:在此匹配仪表盘主题。没有使用访问权限时,地图无法跟随卡片显示状态。</string>
<string name="manufacturer">制造商</string>
<string name="mfi_authentication_ready">MFi 认证已就绪</string>
<string name="mfi_certificate_signing_target">MFi 证书与签名目标</string>
<string name="model">型号</string>
<string name="music_buffer">音乐缓冲</string>
<string name="resolution_native">原生</string>
<string name="navigation_on_hud_and_instrument_cluster">在 HUD 和仪表盘显示导航</string>
<string name="navigation_stream_type">导航音频流类型</string>
<string name="nearby_devices">附近的设备</string>
<string name="nearby_devices_connects_your_iphone_microphone_enables_sir">“附近的设备”权限用于连接 iPhone;麦克风权限用于 Siri 和通话。较旧的 Android 版本还需要位置权限来设置无线连接。USB 模式可能会请求建立本地 VPN 连接。</string>
<string name="nothing_is_sent_automatically_protocol_payloads_and_creden">报告不会自动发送,不包含协议数据和凭据。</string>
<string name="oem_label">OEM 标签</string>
<string name="on_your_iphone_open_settings_bluetooth_and_pair_with_the_c">在 iPhone 上打开“设置 → 蓝牙”并与车机配对。然后返回 DiPlay,点击“连接手机”。</string>
<string name="one_time_setup_on_this_car">在此车机上一次性设置</string>
<string name="open">开放</string>
<string name="open_after_the_car_starts">车机启动后打开</string>
<string name="open_bluetooth">打开蓝牙</string>
<string name="open_car_hotspot_settings">打开车机热点设置</string>
<string name="open_car_settings">打开车机设置</string>
<string name="open_car_wi_fi_settings">打开车机 Wi-Fi 设置</string>
<string name="open_carplay">打开 CarPlay</string>
<string name="open_connection_setup">打开连接设置</string>
<string name="open_this_setting_from_your_car_s_settings_app">请在车机的设置应用中打开此设置。</string>
<string name="opening_carplay">正在打开 CarPlay…</string>
<string name="opening_usb_data_paths">正在打开 USB 数据通道</string>
<string name="pair_another">配对其他设备</string>
<string name="pair_your_iphone">配对 iPhone</string>
<string name="pair_your_iphone_with_the_car_s_bluetooth_keep_wi_fi_on_an">在车机蓝牙设置中配对 iPhone,保持 Wi-Fi 开启,并在 iPhone 上允许 CarPlay。请关闭其他手机投屏应用。\n\n如果之前的投屏应用残留了连接,请先重置下方的 CarPlay Wi-Fi,再重新连接。车机正常的互联网 Wi-Fi 不受影响。</string>
<string name="paired_device">已配对设备</string>
<string name="pairing_with_iphone">正在与 iPhone 配对</string>
<string name="password_must_be_empty_when_security_is_open">安全类型为“开放”时,密码必须留空</string>
<string name="permissions_and_connection_help">权限与连接帮助</string>
<string name="physical_length">物理长度</string>
<string name="physical_size_basis">物理尺寸依据</string>
<string name="place_carplay_s_controls_closer_to_the_driver">将 CarPlay 控件移至更靠近驾驶员的一侧。</string>
<string name="plug_your_iphone_into_a_usb_data_port_allow_carplay_when_y">将 iPhone 连接到支持数据传输的 USB 接口。\n手机出现提示时允许使用 CarPlay。</string>
<string name="prefer_a_cable">想用数据线?</string>
<string name="preparing_mfi_authentication">正在准备 MFi 认证</string>
<string name="ready">就绪</string>
<string name="ready_when_you_are">一切就绪</string>
<string name="receiver_based_on_xcertplay_licensed_under_gpl_3_0_diplay">接收器基于采用 GPL-3.0 许可的 xcertplay。DiPlay 界面沿用采用 AGPL-3.0 许可的 DiAuto 设计。\n\n项目包含 AndroidX、Bouncy Castle、JmDNS 和 SLF4J。本版本附有源代码和许可声明。\n\nCarPlay 和 CarPlay 图标归 Apple Inc. 所有。DiPlay 是独立项目。</string>
<string name="reconnecting_to_your_iphone">正在重新连接 iPhone…</string>
<string name="remote">远程</string>
<string name="remote_server_address_contains_u_0000">远程服务器地址包含 U+0000</string>
<string name="remote_server_address_is_required">必须填写远程服务器地址</string>
<string name="remote_server_address_must_start_with_http_or_https">远程服务器地址必须以 http:// 或 https:// 开头</string>
<string name="remote_token_contains_u_0000">远程令牌包含 U+0000</string>
<string name="report_android_location_to_the_iphone">向 iPhone 报告 Android 位置</string>
<string name="report_location_to_iphone">向 iPhone 报告位置</string>
<string name="report_saved_open_it_from_your_file_manager_to_share_it">报告已保存。在文件管理器中打开即可分享。</string>
<string name="reports_save_to_downloads_diplay">报告将保存到 Downloads/DiPlay。</string>
<string name="requesting_iphone_usb_permission">正在请求 iPhone USB 权限</string>
<string name="requesting_mfi_usb_permission">正在请求 MFi USB 权限</string>
<string name="reset">重置</string>
<string name="reset_and_connect">重置并连接</string>
<string name="reset_car_marker_to_centre">将车辆标记重置到居中</string>
<string name="reset_carplay_wi_fi">重置 CarPlay Wi-Fi</string>
<string name="reset_carplay_wi_fi_2">重置 CarPlay Wi-Fi?</string>
<string name="resolution">分辨率</string>
<string name="return_from_car_settings_to_diplay_then_connect_accept_the">从车机设置返回 DiPlay,然后连接。在 iPhone 上接受 CarPlay 提示。无需车机流量套餐;移动数据可用性取决于手机的网络设置。</string>
<string name="review_app_permissions">查看应用权限</string>
<string name="right_hand_drive">右舵</string>
<string name="running">运行中</string>
<string name="s_0_3x">0.3x</string>
<string name="s_1000_ms_most_stable">1000 毫秒 · 更稳定</string>
<string name="s_1_0x">1.0x</string>
<string name="s_1_choose_your_connection">1 · 选择连接方式</string>
<string name="s_1_connect_a_computer_with_adb_installed_to_the_car_using">1. 用装有 ADB 的电脑,通过现有的 USB 或无线调试连接连接到车机。如有调试授权提示请批准。\n\n2. 在电脑终端中运行此命令:</string>
<string name="s_1_open_car_hotspot_settings_turn_the_hotspot_on_and_sele">1. 打开车机热点设置,开启热点,如有 5 GHz 请选择。\n2. 在下方准确复制热点名称和密码。\n3. 连接时保持车机热点开启。DiPlay 通过蓝牙发送热点信息,iPhone 会自动加入。</string>
<string name="s_2_4_ghz">2.4 GHz</string>
<string name="s_2_pair_your_iphone">2 · 配对你的 iPhone</string>
<string name="s_300_ms_default">300 毫秒 · 默认</string>
<string name="s_30_fps_lighter_load">30 帧/秒 · 减轻负载</string>
<string name="s_3_connect">3 · 连接</string>
<string name="s_3_tap_check_and_enable_below_this_enables_the_cluster_ma">3. 点击下方“检查并启用”。这将启用仪表盘地图和自动主题跟随。正在进行的 CarPlay 会话会重新连接一次,之后即可断开电脑。</string>
<string name="s_500_ms">500 毫秒</string>
<string name="s_5_ghz">5 GHz</string>
<string name="s_60_fps_smoother_motion">60 帧/秒 · 更流畅</string>
<string name="s_60_lightest_load" formatted="false">60% · 最低负载</string>
<string name="s_80_lighter_load" formatted="false">80% · 减轻负载</string>
<string name="safe_area">安全区域</string>
<string name="safe_area_waiting_for_activity_size">安全区域:正在等待活动窗口尺寸</string>
<string name="save">保存</string>
<string name="save_1_1">保存 1:1 图片</string>
<string name="save_and_reconnect">保存并重新连接</string>
<string name="save_details">保存详情</string>
<string name="save_diagnostic_report">保存诊断报告</string>
<string name="save_hotspot_details_and_use_this_mode">保存热点信息并使用此模式</string>
<string name="save_the_name_and_password_from_the_car_s_hotspot_settings">请先保存车机热点设置中的名称和密码</string>
<string name="save_your_hotspot_details_in_connection_setup_first">请先在“连接设置”中保存热点信息</string>
<string name="saved_for_your_next_connection">已保存,将在下次连接时生效</string>
<string name="saving_report">正在保存报告…</string>
<string name="security">安全类型</string>
<string name="selecting_carplay_configuration">正在选择 CarPlay 配置</string>
<string name="sends_precise_android_location_as_carplay_gps_data_when_th">iPhone 请求时,将 Android 精确位置作为 CarPlay GPS 数据发送。</string>
<string name="server_address">服务器地址</string>
<string name="set">设置</string>
<string name="set_up_once_your_details_stay_saved_for_the_next_drive_cha">只需设置一次,信息会保存供下次驾驶使用。更改将在下次连接时生效。</string>
<string name="settings">设置</string>
<string name="setup_needs_attention">设置需要处理</string>
<string name="share">分享</string>
<string name="show_on_screen_debug_logs">在屏幕上显示调试日志</string>
<string name="show_password">显示密码</string>
<string name="show_phone_navigation_arrows_distance_and_street_names_on">在支持的比亚迪显示屏上显示手机导航箭头、距离和道路名称。不同车型的兼容性有所差异。</string>
<string name="show_the_side_map_only_when_its_card_is_open_and_switch_to">仅在地图卡片展开时显示侧方地图,并在“地图主题”中切换为完整地图。更改主题或卡片不会中断 CarPlay 连接。</string>
<string name="shows_the_iphone_s_cluster_map_on_the_instrument_cluster_c">在仪表盘上显示 iPhone 的地图。可在方向盘菜单中选择“小”或“全屏导航”。</string>
<string name="start_carplay_automatically_after_device_boot">设备启动后自动启动 CarPlay</string>
<string name="starting">正在启动</string>
<string name="starting_airplay_service">正在启动 AirPlay 服务</string>
<string name="starting_wireless_hotspot">正在启动无线热点</string>
<string name="startup">启动</string>
<string name="still_waiting_for_usage_access_check_that_the_command_ran">仍在等待使用访问权限。请确认命令已在本车机上成功运行,然后重试。</string>
<string name="tap_a_number_to_test_14_driver_speaker_on_byd">点按数字测试。14 = 比亚迪主驾扬声器。</string>
<string name="the_head_unit_couldn_t_start_carplay_wi_fi_check_wi_fi_and">车机无法启动 CarPlay Wi-Fi。请检查 Wi-Fi 并关闭其他投屏应用,正在重试…</string>
<string name="this_ends_the_existing_wi_fi_direct_connection_including_o">这会结束现有的 Wi-Fi Direct 连接,包括重新安装后遗留的连接。请先关闭其他投屏应用。车机的互联网 Wi-Fi 不受影响。</string>
<string name="this_head_unit_cannot_use_the_smaller_size_at_this_resolut">此车机在当前分辨率下无法使用更小的尺寸,将使用默认尺寸。</string>
<string name="this_head_unit_could_not_open_a_save_location_please_try_s">车机无法打开保存位置,请重试保存到 Downloads。</string>
<string name="this_head_unit_does_not_support_wi_fi_direct">此车机不支持 Wi-Fi Direct。</string>
<string name="this_head_unit_has_no_available_file_picker_to_save_the_re">此车机没有可用于保存报告的文件选择器。</string>
<string name="this_head_unit_may_not_support_wireless_carplay_try_a_usb">此车机可能不支持无线 CarPlay,请尝试 USB 连接。</string>
<string name="token_optional">令牌(可选)</string>
<string name="transport_error_reconnecting">传输错误;正在重新连接</string>
<string name="turn_on_bluetooth">打开蓝牙</string>
<string name="turn_on_wi_fi_in_the_head_unit_s_settings_to_connect">请在车机设置中打开 Wi-Fi 后再连接。</string>
<string name="turn_the_car_s_wi_fi_switch_on_allow_location_nearby_devic">打开车机的 Wi-Fi 开关。如提示请允许位置/附近的设备权限,并在 Android 询问时启用位置。如果播放卡顿,请尝试使用 5 GHz 的内置车机热点。</string>
<string name="usage_access_enabled">使用访问权限:已启用</string>
<string name="usage_access_lets_diplay_follow_the_instrument_theme_and_m">使用访问权限让 DiPlay 跟随仪表盘主题和地图卡片。仅在本地处理 BYD 仪表盘活动事件。本车机没有可用的权限设置页面。</string>
<string name="usage_access_setup_needed_for_automatic_mode">使用访问权限:自动模式需要设置</string>
<string name="usb_ch341">USB/CH341</string>
<string name="use_a_usb_data_cable_and_the_car_s_usb_data_port_unlock_yo">使用 USB 数据线连接车机的 USB 数据接口。解锁 iPhone 并允许 CarPlay。无需设置热点。</string>
<string name="use_a_usb_data_cable_and_unlock_your_iphone_allow_trust_an">请使用 USB 数据线并解锁 iPhone。\niPhone 出现提示时允许“信任”和使用 CarPlay。</string>
<string name="use_hevc_leave_off_for_the_widest_head_unit_compatibility">使用 HEVC。关闭后可兼容更多车机。</string>
<string name="use_software_hevc_decoder">使用 HEVC 软件解码器</string>
<string name="use_usage_content_type_routing_instead_of_stream_type">使用 usage / content-type 路由代替流类型</string>
<string name="use_your_last_connection_type_and_selected_iphone">使用上次的连接方式和已选 iPhone。</string>
<string name="vpn_consent_was_denied">VPN 授权被拒绝</string>
<string name="waiting_for_iphone_over_usb">正在等待 USB 连接的 iPhone</string>
<string name="waiting_for_mfi_coprocessor">正在等待 MFi 协处理器</string>
<string name="waiting_for_paired_iphone">正在等待已配对的 iPhone</string>
<string name="wi_fi_direct_is_still_busy_close_the_other_projection_app">Wi-Fi Direct 仍被占用。请关闭其他投屏应用后重试。</string>
<string name="wi_fi_p2p_5_ghz">Wi-Fi P2P(5 GHz)</string>
<string name="wi_fi_session">Wi-Fi 会话</string>
<string name="widest_width">较宽边</string>
<string name="window">窗口</string>
<string name="wireless_carplay">无线 CarPlay</string>
<string name="wireless_carplay_2">无线 CarPlay</string>
<string name="wireless_carplay_active">无线 CarPlay 已启用</string>
<string name="wireless_carplay_control_running">无线 CarPlay 控制通道运行中</string>
<string name="wireless_carplay_transport">无线 CarPlay 连接</string>
<string name="wireless_connection_help">无线连接帮助</string>
<string name="wireless_hotspot_off">无线热点:关闭</string>
<string name="wireless_permissions">无线连接权限</string>
<string name="wpa2">WPA2</string>
<string name="wpa2_wpa3_password_must_be_8_63_characters">WPA2/WPA3 密码长度必须为 8–63 个字符</string>
<string name="wpa3">WPA3</string>
<string name="wpa3_transition">WPA3 过渡模式</string>
<string name="your_drive_your_way">按你的习惯设置。</string>
<string name="your_iphone_isn_t_available_unlock_it_and_check_bluetooth">暂时找不到 iPhone。请解锁手机并检查蓝牙。</string>
<string name="your_maps_music_and_conversations_carplay_right_here_on_yo">地图、音乐与通话,\n在车机屏幕上畅享 CarPlay。</string>
<string name="your_phone_your_drive">你的手机,畅享旅程。</string>
<string name="your_report_was_saved_to_the_selected_location">报告已保存到所选位置。</string>
</resources>
+331
View File
@@ -1,4 +1,19 @@
<resources>
<string name="msg_car_hotspot_off">The car hotspot “%1$s” is off. Turn it on in the car settings before connecting.</string>
<string name="home_public_preview">PUBLIC PREVIEW ·\u0020\u0020</string>
<string name="choose_iphone_prefix">Choose iPhone ·\u0020</string>
<string name="audio_saved_value">Saved: %1$d</string>
<string name="about_public_preview_prefix">Public preview ·\u0020</string>
<string name="msg_car_hotspot_connect">DiPlay connects through the car hotspot “%1$s”. Turn it on in the car settings, then connect.</string>
<string name="edit_saved_hotspot_prefix">Edit saved hotspot ·\u0020</string>
<string name="cluster_adb_multi_device">If ADB lists several devices, use adb -s &lt;car-device-ID&gt; shell appops set %1$s GET_USAGE_STATS allow. Find the ID with adb devices.</string>
<string name="status_ready_for_prefix">Ready for\u0020</string>
<string name="audio_stream_unavailable">Stream type %1$d is unavailable here (state=%2$s)</string>
<string name="resolution_handshake_prefix">Handshake resolution:\u0020</string>
<string name="safe_area_full_screen_at">Safe area: full screen at\u0020</string>
<string name="safe_area_prefix">Safe area:\u0020</string>
<string name="settings_android9_compat">The following settings are unavailable and hidden on Android 9 (API 28):\n• Wi-Fi P2P (5 GHz) — LocalOnlyHotspot is used instead.\n• HEVC software decoder — hardware decoding is used instead.</string>
<string name="settings_mfi_address_hint">Address must start with http:// or https://. Token is optional and sent as an Authorization bearer token.</string>
<string name="app_name">DiPlay</string>
<string name="language_section_title">Language</string>
<string name="language_app_language">App language</string>
@@ -6,4 +21,320 @@
<string name="language_hint">Choose a language for DiPlay. System default follows the device language.</string>
<string name="language_apply">Apply</string>
<string name="common_cancel">Cancel</string>
<string name="a_familiar_drive">A familiar drive.</string>
<string name="a_previous_wi_fi_direct_connection_is_still_running_reset">A previous Wi-Fi Direct connection is still running. Reset it to connect.</string>
<string name="about">About</string>
<string name="about_diplay">About DiPlay</string>
<string name="active">Active</string>
<string name="advanced_audio_channel_mapping">Advanced audio channel mapping</string>
<string name="airplay_icon">AirPlay icon</string>
<string name="allow_carplay_ui_outside_the_safe_area">Allow CarPlay UI outside the safe area</string>
<string name="allow_nearby_devices_and_on_older_android_versions_locatio">Allow Nearby devices and, on older Android versions, Location before resetting CarPlay Wi-Fi.</string>
<string name="allow_nearby_devices_for_diplay_in_the_head_unit_s_app_per">Allow Nearby devices for DiPlay in the head unit’s app permissions.</string>
<string name="allow_nearby_devices_so_diplay_can_connect_to_your_paired">Allow Nearby devices so DiPlay can connect to your paired iPhone.</string>
<string name="allow_precise_location_for_diplay_in_the_head_unit_s_app_p">Allow precise Location for DiPlay in the head unit’s app permissions.</string>
<string name="allow_the_connection_permission_to_continue">Allow the connection permission to continue</string>
<string name="an_independent_carplay_receiver_for_android_head_units_wir">An independent CarPlay receiver for Android head units. Wired and wireless connections run on the head unit, with local authentication. A standard iPhone can connect without a jailbreak, Mac, dongle or sign-in.\n\nThis preview uses an experimental accessory identity. Compatibility with every iPhone and head unit is still being tested. It is not an Apple-certified product.</string>
<string name="android_9_compatibility">Android 9 compatibility</string>
<string name="app_permissions">App permissions</string>
<string name="app_settings">App settings</string>
<string name="apply_and_reconnect">Apply and reconnect</string>
<string name="apply_reconnects_carplay_for_size_resolution_music_buffer">Apply reconnects CarPlay for size, resolution, music buffer and frame rate. Other changes apply to your next connection.</string>
<string name="audio">Audio</string>
<string name="audio_routing">Audio routing</string>
<string name="auto">Auto</string>
<string name="auto_start_on_boot">Auto-start on boot</string>
<string name="automatic_cluster_map_setup">Automatic cluster map setup</string>
<string name="automatic_connection">Automatic connection</string>
<string name="automatic_map_enabled_open_the_cluster_map_card_or_select">Automatic map enabled. Open the cluster map card or select Map theme.</string>
<string name="automatic_map_setup_adb">Automatic map setup · ADB</string>
<string name="availability_depends_on_your_head_unit_s_startup_settings">Availability depends on your head unit’s startup settings.</string>
<string name="back">Back</string>
<string name="back_to_diplay">Back to DiPlay</string>
<string name="band">Band</string>
<string name="bluetooth_settings">Bluetooth settings</string>
<string name="byd_navigation">BYD navigation</string>
<string name="cancel">Cancel</string>
<string name="car_home">Car home</string>
<string name="car_hotspot_details">Car hotspot details</string>
<string name="car_hotspot_is_off">Car hotspot is off</string>
<string name="car_marker_horizontal">Car marker · horizontal</string>
<string name="car_marker_vertical">Car marker · vertical</string>
<string name="carplay">CarPlay</string>
<string name="carplay_at_home_in_your_car">CarPlay, at home in your car.</string>
<string name="carplay_connected">CarPlay connected</string>
<string name="carplay_control_running">CarPlay control running</string>
<string name="carplay_control_window_ended">CarPlay control window ended</string>
<string name="carplay_icon">CarPlay icon</string>
<string name="carplay_map_on_instrument_cluster_experimental">CarPlay map on instrument cluster · experimental</string>
<string name="carplay_service_already_running">CarPlay service already running</string>
<string name="carplay_session_already_running">CarPlay session already running</string>
<string name="carplay_session_ended_reconnecting">CarPlay session ended; reconnecting</string>
<string name="carplay_settings">CarPlay settings</string>
<string name="carplay_size">CarPlay size</string>
<string name="changes_the_size_of_carplay_icons_and_text_applying_a_size">Changes the size of CarPlay icons and text. Applying a size reconnects CarPlay.</string>
<string name="channel_0_auto">Channel (0 = auto)</string>
<string name="channel_is_not_valid_for_the_selected_band">Channel is not valid for the selected band</string>
<string name="channel_must_be_0_or_1_196">Channel must be 0 or 1-196</string>
<string name="check_and_enable">Check and enable</string>
<string name="check_that_storage_is_available_or_choose_another_save_loc">Check that storage is available, or choose another save location.</string>
<string name="choose_how_to_connect_follow_the_setup_steps_and_save_your">Choose how to connect, follow the setup steps and save your car hotspot details.</string>
<string name="choose_image">Choose image</string>
<string name="choose_iphone">Choose iPhone</string>
<string name="choose_location">Choose location</string>
<string name="choose_save_location">Choose save location</string>
<string name="choose_where_to_save_your_report">Choose where to save your report.\u0020</string>
<string name="choose_your_iphone">Choose your iPhone</string>
<string name="close">Close</string>
<string name="cluster_map_size">Cluster map size</string>
<string name="connect">Connect</string>
<string name="connect_phone">Connect phone</string>
<string name="connect_when_diplay_opens">Connect when DiPlay opens</string>
<string name="connect_with_usb">Connect with USB</string>
<string name="connect_your_iphone_with_a_usb_cable">Connect your iPhone with a USB cable</string>
<string name="connecting_bluetooth">Connecting Bluetooth</string>
<string name="connecting_iap2_control">Connecting iAP2 control</string>
<string name="connecting_to_your_iphone">Connecting to your iPhone…</string>
<string name="connection">Connection</string>
<string name="connection_interrupted_retrying">Connection interrupted. Retrying…</string>
<string name="connection_setup">Connection setup</string>
<string name="copied_to_the_car_clipboard_run_the_command_on_your_comput">Copied to the car clipboard. Run the command on your computer.</string>
<string name="copy_command">Copy command</string>
<string name="copy_these_from_the_car_s_hotspot_settings_use_5_ghz_if_av">Copy these from the car’s hotspot settings. Use 5 GHz if available. Saving here does not change the car’s hotspot.</string>
<string name="could_not_decode_image">Could not decode image</string>
<string name="could_not_encode_image">Could not encode image</string>
<string name="could_not_reset_wi_fi_direct_close_the_other_projection_ap">Could not reset Wi-Fi Direct. Close the other projection app and try again.</string>
<string name="could_not_save_image">Could not save image</string>
<string name="could_not_save_the_report">Could not save the report</string>
<string name="could_not_start_carplay_return_to_diplay_and_check_app_per">Could not start CarPlay. Return to DiPlay and check app permissions.</string>
<string name="custom_1_1_icon">Custom 1:1 icon</string>
<string name="debug_logs">Debug logs</string>
<string name="default_icon">Default icon</string>
<string name="default_placeholder_icon">Default placeholder icon</string>
<string name="diagnostic_report_saved">Diagnostic report saved</string>
<string name="diagnostics">Diagnostics</string>
<string name="diplay">DiPlay</string>
<string name="discard_changes_and_exit_settings">Discard changes and exit settings</string>
<string name="disconnect">Disconnect</string>
<string name="discovering_iphone">Discovering iPhone</string>
<string name="display_and_performance">Display and performance</string>
<string name="display_video">Display &amp; video</string>
<string name="done">Done</string>
<string name="drag_to_move_pinch_to_zoom">Drag to move, pinch to zoom</string>
<string name="draw_outside_safe_area">Draw outside safe area</string>
<string name="driving_side">Driving side</string>
<string name="efficient_video">Efficient video</string>
<string name="enable_the_car_s_bluetooth_and_pair_your_iphone_first">Enable the car’s Bluetooth and pair your iPhone first.</string>
<string name="error">Error</string>
<string name="exit_application">EXIT APPLICATION</string>
<string name="finish_setup_save_your_hotspot_details_to_use_this_mode">Finish setup · Save your hotspot details to use this mode.</string>
<string name="follow_instrument_theme_and_map_card">Follow instrument theme and map card</string>
<string name="frame_rate">Frame rate</string>
<string name="full_screen">Full screen</string>
<string name="fullscreen">Fullscreen</string>
<string name="getting_carplay_ready">Getting CarPlay ready…</string>
<string name="got_it">Got it</string>
<string name="handshake_resolution_waiting_for_display">Handshake resolution: waiting for display</string>
<string name="hevc_h_265_video_transport">HEVC H.265 video transport</string>
<string name="hevc_software_decoder">HEVC software decoder</string>
<string name="hide_bottom_bar">Hide bottom bar</string>
<string name="hide_keyboard">Hide keyboard</string>
<string name="hide_the_car_s_system_bars_while_carplay_is_open">Hide the car’s system bars while CarPlay is open.</string>
<string name="hide_the_navigation_bar">Hide the navigation bar</string>
<string name="hide_the_status_bar">Hide the status bar</string>
<string name="hide_top_bar">Hide top bar</string>
<string name="hotspot_name">Hotspot name</string>
<string name="hotspot_password">Hotspot password</string>
<string name="hotspot_password_contains_u_0000">Hotspot password contains U+0000</string>
<string name="hotspot_setup">Hotspot setup</string>
<string name="hotspot_ssid">Hotspot SSID</string>
<string name="hotspot_ssid_contains_u_0000">Hotspot SSID contains U+0000</string>
<string name="hotspot_ssid_is_required">Hotspot SSID is required</string>
<string name="hotspot_ssid_must_be_at_most_32_utf_8_bytes">Hotspot SSID must be at most 32 UTF-8 bytes</string>
<string name="hotspot_status">Hotspot status</string>
<string name="i2c">I2C</string>
<string name="i2c_device">I2C device</string>
<string name="i2c_device_path_contains_u_0000">I2C device path contains U+0000</string>
<string name="i2c_device_path_is_required">I2C device path is required</string>
<string name="identity_appearance">Identity &amp; appearance</string>
<string name="image_is_not_ready">Image is not ready</string>
<string name="in_carplay_swipe_down_with_three_fingers_to_open_diplay_se">In CarPlay, swipe down with three fingers to open DiPlay settings.</string>
<string name="instrument_contrast">Instrument contrast</string>
<string name="instrument_theme">Instrument theme</string>
<string name="keep_bluetooth_and_wi_fi_on_your_iphone_pair_with_the_car">Keep Bluetooth and Wi-Fi on your iPhone. Pair with the car’s Bluetooth, then select your iPhone here. Allow Nearby devices when asked.</string>
<string name="keep_your_iphone_nearby_with_bluetooth_and_wi_fi_on_allow">Keep your iPhone nearby with Bluetooth and Wi-Fi on.\nAllow CarPlay if your iPhone asks.</string>
<string name="later">Later</string>
<string name="left_hand_drive">Left-hand drive</string>
<string name="linux_device_path_for_example_dev_i2c_1">Linux device path, for example /dev/i2c-1.</string>
<string name="loading_image">Loading image</string>
<string name="local_offline">Local offline</string>
<string name="localonlyhotspot">LocalOnlyHotspot</string>
<string name="location">Location</string>
<string name="longest_height">Longest height</string>
<string name="looking_for_your_paired_iphone">Looking for your paired iPhone…</string>
<string name="made_possible_by_open_source">Made possible by open source</string>
<string name="make_diplay_feel_right_for_your_car">Make DiPlay feel right for your car.</string>
<string name="manual_hotspot">Manual hotspot</string>
<string name="manual_mode_match_the_cluster_theme_here_the_map_cannot_fo">Manual mode: match the cluster theme here. The map cannot follow card visibility without Usage Access.</string>
<string name="manufacturer">Manufacturer</string>
<string name="mfi_authentication_ready">MFi authentication ready</string>
<string name="mfi_certificate_signing_target">MFI certificate &amp; signing target</string>
<string name="model">Model</string>
<string name="music_buffer">Music buffer</string>
<string name="resolution_native">Native</string>
<string name="navigation_on_hud_and_instrument_cluster">Navigation on HUD and instrument cluster</string>
<string name="navigation_stream_type">Navigation stream type</string>
<string name="nearby_devices">Nearby devices</string>
<string name="nearby_devices_connects_your_iphone_microphone_enables_sir">Nearby devices connects your iPhone. Microphone enables Siri and calls. Older Android versions also require Location for wireless setup. USB mode may ask for a local VPN connection.</string>
<string name="nothing_is_sent_automatically_protocol_payloads_and_creden">Nothing is sent automatically. Protocol payloads and credentials are excluded.</string>
<string name="oem_label">OEM label</string>
<string name="on_your_iphone_open_settings_bluetooth_and_pair_with_the_c">On your iPhone, open Settings → Bluetooth and pair with the car. Then return to DiPlay and choose Connect phone.</string>
<string name="one_time_setup_on_this_car">One-time setup on this car</string>
<string name="open">Open</string>
<string name="open_after_the_car_starts">Open after the car starts</string>
<string name="open_bluetooth">Open Bluetooth</string>
<string name="open_car_hotspot_settings">Open car hotspot settings</string>
<string name="open_car_settings">Open car settings</string>
<string name="open_car_wi_fi_settings">Open car Wi-Fi settings</string>
<string name="open_carplay">Open CarPlay</string>
<string name="open_connection_setup">Open connection setup</string>
<string name="open_this_setting_from_your_car_s_settings_app">Open this setting from your car’s Settings app.</string>
<string name="opening_carplay">Opening CarPlay…</string>
<string name="opening_usb_data_paths">Opening USB data paths</string>
<string name="pair_another">Pair another</string>
<string name="pair_your_iphone">Pair your iPhone</string>
<string name="pair_your_iphone_with_the_car_s_bluetooth_keep_wi_fi_on_an">Pair your iPhone with the car’s Bluetooth, keep Wi-Fi on, and allow CarPlay on the iPhone. Close any other phone-projection app.\n\nIf a previous projection app left its connection running, reset CarPlay Wi-Fi below and connect again. Your car’s normal internet Wi-Fi stays on.</string>
<string name="paired_device">Paired device</string>
<string name="pairing_with_iphone">Pairing with iPhone</string>
<string name="password_must_be_empty_when_security_is_open">Password must be empty when security is Open</string>
<string name="permissions_and_connection_help">Permissions and connection help</string>
<string name="physical_length">Physical length</string>
<string name="physical_size_basis">Physical size basis</string>
<string name="place_carplay_s_controls_closer_to_the_driver">Place CarPlay’s controls closer to the driver.</string>
<string name="plug_your_iphone_into_a_usb_data_port_allow_carplay_when_y">Plug your iPhone into a USB data port.\nAllow CarPlay when your iPhone asks.</string>
<string name="prefer_a_cable">Prefer a cable?</string>
<string name="preparing_mfi_authentication">Preparing MFi authentication</string>
<string name="ready">Ready</string>
<string name="ready_when_you_are">Ready when you are</string>
<string name="receiver_based_on_xcertplay_licensed_under_gpl_3_0_diplay">Receiver based on xcertplay, licensed under GPL-3.0. DiPlay’s interface follows DiAuto’s design, licensed under AGPL-3.0.\n\nIncludes AndroidX, Bouncy Castle, JmDNS and SLF4J. Source and license notices accompany this release.\n\nCarPlay and the CarPlay icon belong to Apple Inc. DiPlay is an independent project.</string>
<string name="reconnecting_to_your_iphone">Reconnecting to your iPhone…</string>
<string name="remote">Remote</string>
<string name="remote_server_address_contains_u_0000">Remote server address contains U+0000</string>
<string name="remote_server_address_is_required">Remote server address is required</string>
<string name="remote_server_address_must_start_with_http_or_https">Remote server address must start with http:// or https://</string>
<string name="remote_token_contains_u_0000">Remote token contains U+0000</string>
<string name="report_android_location_to_the_iphone">Report Android location to the iPhone</string>
<string name="report_location_to_iphone">Report location to iPhone</string>
<string name="report_saved_open_it_from_your_file_manager_to_share_it">Report saved. Open it from your file manager to share it.</string>
<string name="reports_save_to_downloads_diplay">Reports save to Downloads/DiPlay.\u0020</string>
<string name="requesting_iphone_usb_permission">Requesting iPhone USB permission</string>
<string name="requesting_mfi_usb_permission">Requesting MFi USB permission</string>
<string name="reset">Reset</string>
<string name="reset_and_connect">Reset and connect</string>
<string name="reset_car_marker_to_centre">Reset car marker to centre</string>
<string name="reset_carplay_wi_fi">Reset CarPlay Wi-Fi</string>
<string name="reset_carplay_wi_fi_2">Reset CarPlay Wi-Fi?</string>
<string name="resolution">Resolution</string>
<string name="return_from_car_settings_to_diplay_then_connect_accept_the">Return from car settings to DiPlay, then connect. Accept the CarPlay prompt on your iPhone. A car internet plan is not required; mobile data availability depends on your phone’s network settings.</string>
<string name="review_app_permissions">Review app permissions</string>
<string name="right_hand_drive">Right-hand drive</string>
<string name="running">Running</string>
<string name="s_0_3x">0.3x</string>
<string name="s_1000_ms_most_stable">1000 ms · most stable</string>
<string name="s_1_0x">1.0x</string>
<string name="s_1_choose_your_connection">1 · Choose your connection</string>
<string name="s_1_connect_a_computer_with_adb_installed_to_the_car_using">1. Connect a computer with ADB installed to the car using your existing USB or wireless debugging connection. Approve debugging on the car if asked.\n\n2. Run this command in the computer’s terminal:</string>
<string name="s_1_open_car_hotspot_settings_turn_the_hotspot_on_and_sele">1. Open car hotspot settings, turn the hotspot on and select 5 GHz if available.\n2. Copy its name and password below exactly.\n3. Leave the car hotspot on when connecting. DiPlay sends the details over Bluetooth; your iPhone joins automatically.</string>
<string name="s_2_4_ghz">2.4 GHz</string>
<string name="s_2_pair_your_iphone">2 · Pair your iPhone</string>
<string name="s_300_ms_default">300 ms · default</string>
<string name="s_30_fps_lighter_load">30 fps · lighter load</string>
<string name="s_3_connect">3 · Connect</string>
<string name="s_3_tap_check_and_enable_below_this_enables_the_cluster_ma">3. Tap Check and enable below. This enables the cluster map and automatic theme following. An active CarPlay session reconnects once. You can then disconnect the computer.</string>
<string name="s_500_ms">500 ms</string>
<string name="s_5_ghz">5 GHz</string>
<string name="s_60_fps_smoother_motion">60 fps · smoother motion</string>
<string name="s_60_lightest_load" formatted="false">60% · lightest load</string>
<string name="s_80_lighter_load" formatted="false">80% · lighter load</string>
<string name="safe_area">Safe area</string>
<string name="safe_area_waiting_for_activity_size">Safe area: waiting for activity size</string>
<string name="save">Save</string>
<string name="save_1_1">Save 1:1</string>
<string name="save_and_reconnect">Save and reconnect</string>
<string name="save_details">Save details</string>
<string name="save_diagnostic_report">Save diagnostic report</string>
<string name="save_hotspot_details_and_use_this_mode">Save hotspot details and use this mode</string>
<string name="save_the_name_and_password_from_the_car_s_hotspot_settings">Save the name and password from the car’s hotspot settings first</string>
<string name="save_your_hotspot_details_in_connection_setup_first">Save your hotspot details in Connection setup first</string>
<string name="saved_for_your_next_connection">Saved for your next connection</string>
<string name="saving_report">Saving report…</string>
<string name="security">Security</string>
<string name="selecting_carplay_configuration">Selecting CarPlay configuration</string>
<string name="sends_precise_android_location_as_carplay_gps_data_when_th">Sends precise Android location as CarPlay GPS data when the iPhone requests it.</string>
<string name="server_address">Server address</string>
<string name="set">Set</string>
<string name="set_up_once_your_details_stay_saved_for_the_next_drive_cha">Set up once. Your details stay saved for the next drive. Changes apply to your next connection.</string>
<string name="settings">Settings</string>
<string name="setup_needs_attention">Setup needs attention</string>
<string name="share">Share</string>
<string name="show_on_screen_debug_logs">Show on-screen debug logs</string>
<string name="show_password">Show password</string>
<string name="show_phone_navigation_arrows_distance_and_street_names_on">Show phone navigation arrows, distance and street names on supported BYD displays. Vehicle compatibility varies.</string>
<string name="show_the_side_map_only_when_its_card_is_open_and_switch_to">Show the side map only when its card is open, and switch to the full map in Map theme. Theme and card changes keep CarPlay connected.</string>
<string name="shows_the_iphone_s_cluster_map_on_the_instrument_cluster_c">Shows the iPhone\'s cluster map on the instrument cluster. Choose Small or Full screen navi in the cluster\'s steering-wheel menu.</string>
<string name="start_carplay_automatically_after_device_boot">Start CarPlay automatically after device boot</string>
<string name="starting">Starting</string>
<string name="starting_airplay_service">Starting AirPlay service</string>
<string name="starting_wireless_hotspot">Starting wireless hotspot</string>
<string name="startup">Startup</string>
<string name="still_waiting_for_usage_access_check_that_the_command_ran">Still waiting for Usage Access. Check that the command ran successfully for this car, then try again.</string>
<string name="tap_a_number_to_test_14_driver_speaker_on_byd">Tap a number to test. 14 = driver speaker on BYD.</string>
<string name="the_head_unit_couldn_t_start_carplay_wi_fi_check_wi_fi_and">The head unit couldn’t start CarPlay Wi-Fi. Check Wi-Fi and close other projection apps. Retrying…</string>
<string name="this_ends_the_existing_wi_fi_direct_connection_including_o">This ends the existing Wi-Fi Direct connection, including one left behind after reinstalling. Close other projection apps first. Your car’s internet Wi-Fi stays on.</string>
<string name="this_head_unit_cannot_use_the_smaller_size_at_this_resolut">This head unit cannot use the smaller size at this resolution. Using Default.</string>
<string name="this_head_unit_could_not_open_a_save_location_please_try_s">This head unit could not open a save location. Please try saving to Downloads again.</string>
<string name="this_head_unit_does_not_support_wi_fi_direct">This head unit does not support Wi-Fi Direct.</string>
<string name="this_head_unit_has_no_available_file_picker_to_save_the_re">This head unit has no available file picker to save the report.</string>
<string name="this_head_unit_may_not_support_wireless_carplay_try_a_usb">This head unit may not support wireless CarPlay. Try a USB connection.</string>
<string name="token_optional">Token (optional)</string>
<string name="transport_error_reconnecting">Transport error; reconnecting</string>
<string name="turn_on_bluetooth">Turn on Bluetooth</string>
<string name="turn_on_wi_fi_in_the_head_unit_s_settings_to_connect">Turn on Wi-Fi in the head unit’s settings to connect.</string>
<string name="turn_the_car_s_wi_fi_switch_on_allow_location_nearby_devic">Turn the car’s Wi-Fi switch on. Allow Location / Nearby devices if requested and enable Location when Android asks. If playback stutters, try the built-in car hotspot at 5 GHz.</string>
<string name="usage_access_enabled">Usage Access: enabled</string>
<string name="usage_access_lets_diplay_follow_the_instrument_theme_and_m">Usage Access lets DiPlay follow the instrument theme and map card. Only BYD cluster activity events are processed locally. This car does not provide a working permission settings page.</string>
<string name="usage_access_setup_needed_for_automatic_mode">Usage Access: setup needed for automatic mode</string>
<string name="usb_ch341">USB/CH341</string>
<string name="use_a_usb_data_cable_and_the_car_s_usb_data_port_unlock_yo">Use a USB data cable and the car’s USB data port. Unlock your iPhone and allow CarPlay. No hotspot setup is needed.</string>
<string name="use_a_usb_data_cable_and_unlock_your_iphone_allow_trust_an">Use a USB data cable and unlock your iPhone.\nAllow Trust and CarPlay if your iPhone asks.</string>
<string name="use_hevc_leave_off_for_the_widest_head_unit_compatibility">Use HEVC. Leave off for the widest head-unit compatibility.</string>
<string name="use_software_hevc_decoder">Use software HEVC decoder</string>
<string name="use_usage_content_type_routing_instead_of_stream_type">Use usage / content-type routing instead of stream type</string>
<string name="use_your_last_connection_type_and_selected_iphone">Use your last connection type and selected iPhone.</string>
<string name="vpn_consent_was_denied">VPN consent was denied</string>
<string name="waiting_for_iphone_over_usb">Waiting for iPhone over USB</string>
<string name="waiting_for_mfi_coprocessor">Waiting for MFi coprocessor</string>
<string name="waiting_for_paired_iphone">Waiting for paired iPhone</string>
<string name="wi_fi_direct_is_still_busy_close_the_other_projection_app">Wi-Fi Direct is still busy. Close the other projection app and try again.</string>
<string name="wi_fi_p2p_5_ghz">Wi-Fi P2P (5 GHz)</string>
<string name="wi_fi_session">Wi-Fi session</string>
<string name="widest_width">Widest width</string>
<string name="window">Window</string>
<string name="wireless_carplay">WIRELESS CARPLAY</string>
<string name="wireless_carplay_2">Wireless CarPlay</string>
<string name="wireless_carplay_active">Wireless CarPlay active</string>
<string name="wireless_carplay_control_running">Wireless CarPlay control running</string>
<string name="wireless_carplay_transport">Wireless CarPlay transport</string>
<string name="wireless_connection_help">Wireless connection help</string>
<string name="wireless_hotspot_off">Wireless hotspot: off</string>
<string name="wireless_permissions">Wireless permissions</string>
<string name="wpa2">WPA2</string>
<string name="wpa2_wpa3_password_must_be_8_63_characters">WPA2/WPA3 password must be 8-63 characters</string>
<string name="wpa3">WPA3</string>
<string name="wpa3_transition">WPA3 transition</string>
<string name="your_drive_your_way">Your drive, your way.</string>
<string name="your_iphone_isn_t_available_unlock_it_and_check_bluetooth">Your iPhone isn’t available. Unlock it and check Bluetooth.</string>
<string name="your_maps_music_and_conversations_carplay_right_here_on_yo">Your maps, music and conversations.\nCarPlay, right here on your car display.</string>
<string name="your_phone_your_drive">YOUR PHONE. YOUR DRIVE.</string>
<string name="your_report_was_saved_to_the_selected_location">Your report was saved to the selected location.</string>
</resources>