Skip to content

Commit c167f66

Browse files
Merge pull request #64 from andreknieriem/translation-and-portrait
Translation and portrait
2 parents 3928503 + d69d1be commit c167f66

30 files changed

Lines changed: 3896 additions & 1364 deletions

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,23 @@ adb shell am start -a android.intent.action.VIEW -d "headunit://connect?ip=192.1
7575
- Portrait Mode (https://github.com/andreknieriem/headunit-revived/issues/38)
7676
- Maybe:
7777
- Android SKD 17
78-
- Auto-Connect last session (if possible)
78+
79+
## Known Issues
80+
- **Google Maps in Portrait Mode:** Touch interactions (searching, scrolling) within Google Maps may not work as expected when using Portrait Mode. While visual feedback (like ripple effects) might appear, the map itself may remain unresponsive. This appears to be an internal Android Auto / Google Maps limitation or bug in vertical orientations.
7981

8082
## Changelog
83+
### v.1.10.0
84+
- New Feature: Portrait Mode Support (Dashboard & Projection) with smart resolution scaling Known Bug is, that map is unresponsive to touch. That is in all HU apps
85+
- New Feature: Redesigned Keymap Screen (easier configuration)
86+
- New Feature: Right Hand side driving setting (#63)
87+
- New Feature: Auto-Connect last session (Thanks to @JanRi3D) (#21)
88+
- New Feature: Auto-Selfmode if enabled in settings
89+
- New Feature: Allow sideloaded apps (#57)
90+
- Localization: Added German Translation 🇩🇪 Other translations are highly appreciated
91+
- Improvement: TextureView is now the default renderer (better compatibility for most devices)
92+
- Improvement: Fixed Dashboard layout rotation
93+
- Rewrite: Completly Rewrite the Video-Decoder as it was undebuggable. Removed the async mode and more
94+
8195
### v.1.9.0
8296
- New Feature: GLES20 Video Renderer (Fixes black screen/artifacts/scaling on older Head Units)
8397
- New Feature: In-App Log Export (Save to file/Share) for easier debugging

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ android {
5050
minSdk = 16
5151
//minSdk = 21 // 21 only for google play console. App should work in minSDK 19 or maybe 17
5252
targetSdk = 36
53-
versionCode = 28
54-
versionName = "1.9.0"
53+
versionCode = 29
54+
versionName = "1.10.0"
5555
setProperty("archivesBaseName", "${applicationId}_${versionName}")
5656
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
5757
multiDexEnabled = true

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
android:name=".aap.AapProjectionActivity"
5050
android:hardwareAccelerated="true"
5151
android:launchMode="singleTop"
52-
android:screenOrientation="sensorLandscape"
52+
android:screenOrientation="sensor"
5353
android:theme="@style/AppTheme" />
5454

5555
<activity
@@ -67,9 +67,9 @@
6767

6868
<activity
6969
android:name=".main.MainActivity"
70-
android:configChanges="orientation|keyboardHidden|screenSize"
70+
android:configChanges="keyboardHidden"
7171
android:launchMode="singleTask"
72-
android:screenOrientation="sensorLandscape"
72+
android:screenOrientation="sensor"
7373
android:theme="@style/SplashTheme"
7474
android:exported="true">
7575

app/src/main/assets/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
### v.1.10.0
4+
- New Feature: Portrait Mode Support (Dashboard & Projection) with smart resolution scaling Known Bug is, that map is unresponsive to touch. That is in all HU apps
5+
- New Feature: Redesigned Keymap Screen (easier configuration)
6+
- New Feature: Right Hand side driving setting (#63)
7+
- New Feature: Auto-Connect last session (Thanks to @JanRi3D) (#21)
8+
- New Feature: Auto-Selfmode if enabled in settings
9+
- New Feature: Allow sideloaded apps (#57)
10+
- Localization: Added German Translation 🇩🇪 Other translations are highly appreciated
11+
- Improvement: TextureView is now the default renderer (better compatibility for most devices)
12+
- Improvement: Fixed Dashboard layout rotation
13+
- Rewrite: Completly Rewrite the Video-Decoder as it was undebuggable. Removed the async mode and more
14+
315
### v.1.9.0
416
- New Feature: GLES20 Video Renderer (Fixes black screen/artifacts/scaling on older Head Units)
517
- New Feature: In-App Log Export (Save to file/Share) for easier debugging

app/src/main/java/com/andrerinas/headunitrevived/aap/AapControl.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ internal class AapControlTouch(private val aapTransport: AapTransport): AapContr
132132
}
133133

134134
private fun inputBinding(request: Input.KeyBindingRequest, channel: Int): Int {
135-
AppLog.i("Input binding request %s", request)
136135
aapTransport.send(AapMessage(channel, Input.MsgType.BINDINGRESPONSE_VALUE, Input.BindingResponse.newBuilder()
137136
.setStatus(Common.MessageStatus.STATUS_SUCCESS)
138137
.build()))
@@ -342,17 +341,12 @@ internal class AapControlGateway(
342341
}
343342

344343
private fun channelOpenRequest(request: Control.ChannelOpenRequest, channel: Int): Int {
345-
AppLog.i("Channel Open Request - priority: %d chan: %d %s", request.priority, request.serviceId, Channel.name(request.serviceId))
346-
347344
val msg = AapMessage(channel, Control.ControlMsgType.MESSAGE_CHANNEL_OPEN_RESPONSE_VALUE, Control.ChannelOpenResponse.newBuilder()
348345
.setStatus(Common.MessageStatus.STATUS_SUCCESS)
349346
.build())
350-
AppLog.i(msg.toString())
351-
352347
aapTransport.send(msg)
353348

354349
if (channel == Channel.ID_SEN) {
355-
AppLog.i("Send driving status")
356350
aapTransport.send(DrivingStatusEvent(Sensors.SensorBatch.DrivingStatusData.Status.UNRESTRICTED))
357351
}
358352
return 0

app/src/main/java/com/andrerinas/headunitrevived/aap/AapProjectionActivity.kt

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import com.andrerinas.headunitrevived.view.TextureProjectionView
2626
import com.andrerinas.headunitrevived.utils.Settings
2727
import com.andrerinas.headunitrevived.view.OverlayTouchView
2828
import com.andrerinas.headunitrevived.utils.HeadUnitScreenConfig
29-
import com.andrerinas.headunitrevived.aap.AapService
3029
import com.andrerinas.headunitrevived.utils.SystemUI
3130

3231
class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, VideoDimensionsListener {
@@ -58,6 +57,14 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
5857

5958
override fun onCreate(savedInstanceState: Bundle?) {
6059
super.onCreate(savedInstanceState)
60+
61+
// Lock orientation to current state
62+
if (Build.VERSION.SDK_INT >= 18) {
63+
requestedOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED
64+
} else {
65+
requestedOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
66+
}
67+
6168
setContentView(R.layout.activity_headunit)
6269

6370
// Register disconnect receiver here to stay active even if activity is paused
@@ -162,12 +169,14 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
162169

163170
override fun onSurfaceChanged(surface: android.view.Surface, width: Int, height: Int) {
164171
AppLog.i("[AapProjectionActivity] onSurfaceChanged. Actual surface dimensions: width=$width, height=$height")
165-
videoDecoder.setSurface(surface)
172+
173+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
174+
AppLog.i("Delayed setting surface to decoder")
175+
videoDecoder.setSurface(surface)
166176

167-
// Force keyframe by toggling focus, prevent quit on resulting stop request
168-
transport.ignoreNextStopRequest = true
169-
transport.send(VideoFocusEvent(gain = false, unsolicited = true))
170-
transport.send(VideoFocusEvent(gain = true, unsolicited = true))
177+
// Simply request focus to ensure stream is active
178+
transport.send(VideoFocusEvent(gain = true, unsolicited = false))
179+
}, 750)
171180

172181
// Explicitly check and set video dimensions if already known by the decoder
173182
// This handles cases where the activity is recreated but the decoder already has dimensions
@@ -185,9 +194,8 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
185194

186195
override fun onSurfaceDestroyed(surface: android.view.Surface) {
187196
AppLog.i("SurfaceCallback: onSurfaceDestroyed. Surface: $surface")
188-
// transport.send(VideoFocusEvent(gain = false, unsolicited = false))
197+
transport.send(VideoFocusEvent(gain = false, unsolicited = false))
189198
videoDecoder.stop("surfaceDestroyed")
190-
videoDecoder.setSurface(null)
191199
}
192200

193201
override fun onVideoDimensionsChanged(width: Int, height: Int) {
@@ -240,6 +248,7 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
240248

241249
override fun onDestroy() {
242250
super.onDestroy()
251+
AppLog.i("AapProjectionActivity.onDestroy called. isFinishing=$isFinishing")
243252
unregisterReceiver(disconnectReceiver)
244253
videoDecoder.dimensionsListener = null
245254

app/src/main/java/com/andrerinas/headunitrevived/aap/AapService.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import com.andrerinas.headunitrevived.utils.NightMode
3737
import com.andrerinas.headunitrevived.utils.Settings
3838
import kotlinx.coroutines.*
3939
import java.net.ServerSocket
40-
import java.net.Socket
4140

4241
class AapService : Service(), UsbReceiver.Listener {
4342
private val serviceJob = Job()
@@ -48,7 +47,7 @@ class AapService : Service(), UsbReceiver.Listener {
4847
private lateinit var usbReceiver: UsbReceiver
4948
private lateinit var nightModeReceiver: BroadcastReceiver
5049
private var wirelessServer: WirelessServer? = null
51-
50+
5251
private var pendingConnectionType: String = ""
5352
private var pendingConnectionIp: String = ""
5453
private var pendingConnectionUsbDevice: String = ""
@@ -65,6 +64,7 @@ class AapService : Service(), UsbReceiver.Listener {
6564
startForeground(1, createNotification())
6665

6766
uiModeManager = getSystemService(UI_MODE_SERVICE) as UiModeManager
67+
uiModeManager.enableCarMode(0)
6868
uiModeManager.nightMode = UiModeManager.MODE_NIGHT_AUTO
6969

7070
usbReceiver = UsbReceiver(this)
@@ -146,7 +146,7 @@ class AapService : Service(), UsbReceiver.Listener {
146146
AppLog.e("Cannot create connection from intent")
147147
return
148148
}
149-
149+
150150
when (connectionType) {
151151
TYPE_USB -> {
152152
val device = DeviceIntent(intent).device
@@ -207,7 +207,7 @@ class AapService : Service(), UsbReceiver.Listener {
207207
pendingConnectionType = Settings.CONNECTION_TYPE_WIFI
208208
pendingConnectionIp = clientSocket.inetAddress.hostAddress ?: ""
209209
pendingConnectionUsbDevice = ""
210-
210+
211211
accessoryConnection = SocketAccessoryConnection(clientSocket)
212212
val success = accessoryConnection!!.connect()
213213
onConnectionResult(success)
@@ -318,15 +318,12 @@ class AapService : Service(), UsbReceiver.Listener {
318318
pendingConnectionIp = ""
319319
pendingConnectionUsbDevice = ""
320320
}
321-
322-
serviceScope.launch {
323-
delay(1000)
324-
val aapIntent = AapProjectionActivity.intent(this@AapService).apply {
325-
putExtra(AapProjectionActivity.EXTRA_FOCUS, true)
326-
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
327-
}
328-
startActivity(aapIntent)
321+
322+
val aapIntent = AapProjectionActivity.intent(this@AapService).apply {
323+
putExtra(AapProjectionActivity.EXTRA_FOCUS, true)
324+
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
329325
}
326+
startActivity(aapIntent)
330327
} else {
331328
stopSelf()
332329
}
@@ -384,8 +381,6 @@ class AapService : Service(), UsbReceiver.Listener {
384381
const val ACTION_START_SELF_MODE = "com.andrerinas.headunitrevived.ACTION_START_SELF_MODE"
385382
const val ACTION_START_WIRELESS = "com.andrerinas.headunitrevived.ACTION_START_WIRELESS"
386383
const val ACTION_STOP_WIRELESS = "com.andrerinas.headunitrevived.ACTION_STOP_WIRELESS"
387-
const val ACTION_START_FROM_PROXY = "com.andrerinas.headunitrevived.ACTION_START_FROM_PROXY" // Legacy
388-
const val EXTRA_LOCAL_PROXY_PORT = "local_proxy_port" // Legacy
389384
const val ACTION_STOP_SERVICE = "com.andrerinas.headunitrevived.ACTION_STOP_SERVICE"
390385
private const val TYPE_USB = 1
391386
private const val TYPE_WIFI = 2

app/src/main/java/com/andrerinas/headunitrevived/aap/AapTransport.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ class AapTransport(
227227

228228
AppLog.i("Handshake: Status OK sent: %d", ret)
229229
AppLog.d("Handshake: Handshake successful. TS: ${SystemClock.elapsedRealtime()}")
230+
230231
return true
231232
} catch (e: Exception) {
232233
AppLog.e("Handshake failed with exception", e)

app/src/main/java/com/andrerinas/headunitrevived/aap/AapVideo.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal class AapVideo(private val videoDecoder: VideoDecoder, private val sett
3030
videoDecoder.decode(buf, 2, len - 2, settings.forceSoftwareDecoding, settings.videoCodec)
3131
return true
3232
}
33+
AppLog.w("AapVideo: Dropped Flag 11 packet. len=$len, buf[10]=${if (len > 10) buf[10] else "?"}")
3334
}
3435
9 -> {
3536
// Timestamp Indication (Offset 10)

app/src/main/java/com/andrerinas/headunitrevived/aap/protocol/messages/ServiceDiscoveryResponse.kt

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ class ServiceDiscoveryResponse(private val context: Context)
8787
service.id = Channel.ID_INP
8888
service.inputSourceService = Control.Service.InputSourceService.newBuilder().also {
8989
it.touchscreen = Control.Service.InputSourceService.TouchConfig.newBuilder().apply {
90-
setWidth(HeadUnitScreenConfig.getAdjustedWidth()) // Use effective width
91-
setHeight(HeadUnitScreenConfig.getAdjustedHeight()) // Use effective height
90+
setWidth(HeadUnitScreenConfig.getNegotiatedWidth()) // Use negotiated width
91+
setHeight(HeadUnitScreenConfig.getNegotiatedHeight()) // Use negotiated height
9292
}.build()
9393
it.addAllKeycodesSupported(KeyCode.supported)
9494
}.build()
@@ -172,16 +172,29 @@ class ServiceDiscoveryResponse(private val context: Context)
172172

173173
return Control.ServiceDiscoveryResponse.newBuilder().apply {
174174
make = "Google"
175-
model = "Android Auto"
176-
year = "2023"
177-
vehicleId = "Generic"
178-
headUnitModel = "Generic Headunit"
179-
headUnitMake = "Generic Make"
180-
headUnitSoftwareBuild = "1.0"
181-
headUnitSoftwareVersion = "1.3.0"
182-
driverPosition = true
175+
model = "Desktop Head Unit"
176+
year = "2025"
177+
vehicleId = "headlessunit-001"
178+
headUnitModel = "Desktop Head Unit"
179+
headUnitMake = "Google"
180+
headUnitSoftwareBuild = "1"
181+
headUnitSoftwareVersion = "0.1.0"
182+
driverPosition = if (settings.rightHandDrive) Control.DriverPosition.DRIVER_POSITION_RIGHT else Control.DriverPosition.DRIVER_POSITION_LEFT
183183
canPlayNativeMediaDuringVr = false
184184
hideProjectedClock = false
185+
setDisplayName("Headunit Revived")
186+
187+
setHeadunitInfo(com.andrerinas.headunitrevived.aap.protocol.proto.Common.HeadUnitInfo.newBuilder().apply {
188+
setHeadUnitMake("Google")
189+
setHeadUnitModel("Desktop Head Unit")
190+
setMake("Google")
191+
setModel("Desktop Head Unit")
192+
setYear("2025")
193+
setVehicleId("headlessunit-001")
194+
setHeadUnitSoftwareBuild("1")
195+
setHeadUnitSoftwareVersion("0.1.0")
196+
}.build())
197+
185198
addAllServices(services)
186199
}.build()
187200
}

0 commit comments

Comments
 (0)