CarInfo and CarSensors

This commit is contained in:
Dimitris
2025-12-31 11:16:41 +01:00
parent 9f356bd728
commit 1eab4f1aa3
13 changed files with 134 additions and 70 deletions

View File

@@ -6,7 +6,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
tools:ignore="MockLocation" />

View File

@@ -29,7 +29,7 @@ class MainApplication : Application() {
super.onCreate()
ObjectBox.init(this);
appContext = applicationContext
setIntKeyValue(appContext!!, RouteEngine.OSRM.ordinal, ROUTE_ENGINE)
setIntKeyValue(appContext!!, RouteEngine.VALHALLA.ordinal, ROUTE_ENGINE)
navigationViewModel = getRouteEngine(appContext!!)
startKoin {
androidLogger(Level.DEBUG)
@@ -43,8 +43,6 @@ class MainApplication : Application() {
var appContext: Context? = null
private set
var useContacts = false
lateinit var navigationViewModel : ViewModel
}
}

View File

@@ -39,20 +39,20 @@ import androidx.lifecycle.Observer
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.location
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.maplibre.compose.camera.CameraPosition
@@ -60,6 +60,7 @@ import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
@@ -101,6 +102,8 @@ class MainActivity : ComponentActivity() {
private var overpass = false
lateinit var baseStyle : BaseStyle.Json
init {
navigationViewModel.route.observe(this, observer)
}
@@ -108,6 +111,8 @@ class MainActivity : ComponentActivity() {
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val darkModeSettings = getIntKeyValue(applicationContext, Constants.DARK_MODE_SETTINGS)
baseStyle = BaseStyleModel().readStyle(applicationContext, darkModeSettings, false)
if (useMock) {
checkMockLocationEnabled()
}
@@ -196,7 +201,8 @@ class MainActivity : ComponentActivity() {
step,
cameraPosition,
routeData,
tilt
tilt,
baseStyle
)
}
}

View File

@@ -7,17 +7,14 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import androidx.window.layout.WindowMetricsCalculator
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.map.DarkMode
import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.NavigationImage
import com.kouros.navigation.data.Constants
import com.kouros.navigation.car.map.rememberBaseStyle
import com.kouros.navigation.data.StepData
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState
@@ -34,10 +31,12 @@ fun MapView(
step: StepData?,
cameraPosition: MutableLiveData<CameraPosition>,
routeData: MutableLiveData<String>,
tilt: Double
tilt: Double,
baseStyle: BaseStyle.Json,
) {
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(applicationContext)
val metrics =
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(applicationContext)
val width = metrics.bounds.width()
val height = metrics.bounds.height()
val paddingValues = PaddingValues(start = 0.dp, top = 350.dp)
@@ -55,21 +54,19 @@ fun MapView(
zoom = 15.0,
)
)
val baseStyle = remember {
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
}
DarkMode(applicationContext, baseStyle)
val rememberBaseStyle = rememberBaseStyle( baseStyle)
Column {
NavigationInfo(step)
Box(contentAlignment = Alignment.Center) {
MapLibre(
applicationContext,
cameraState,
baseStyle,
rememberBaseStyle,
route,
ViewStyle.VIEW
)
LocationTrackingEffect(
LocationTrackingEffect(
locationState = userLocationState,
) {
cameraState.animateTo(

View File

@@ -46,6 +46,7 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.ui)
implementation(libs.maplibre.compose)
implementation(libs.androidx.app.projected)
//implementation(libs.maplibre.composeMaterial3)
implementation(project(":common:data"))

View File

@@ -32,6 +32,8 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
<uses-permission android:name="com.google.android.gms.permission.CAR_SPEED"/>
<uses-permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"/>
<application android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true">

View File

@@ -38,9 +38,4 @@ class NavigationCarAppService : CarAppService() {
}
}
public interface LocationCallback {
fun onLocationChanged(location: Location) {
}
}

View File

@@ -5,6 +5,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.location.Location
import android.location.LocationManager
import android.util.Log
@@ -12,6 +13,13 @@ import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.ScreenManager
import androidx.car.app.Session
import androidx.car.app.hardware.CarHardwareManager
import androidx.car.app.hardware.common.CarValue
import androidx.car.app.hardware.common.OnCarDataAvailableListener
import androidx.car.app.hardware.info.CarHardwareLocation
import androidx.car.app.hardware.info.CarSensors
import androidx.car.app.hardware.info.Speed
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.core.location.LocationListenerCompat
import androidx.core.net.toUri
import androidx.lifecycle.DefaultLifecycleObserver
@@ -22,17 +30,16 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
import org.maplibre.compose.style.BaseStyle
class NavigationSession : Session(), NavigationScreen.Listener {
@@ -66,6 +73,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
}
override fun onDestroy(owner: LifecycleOwner) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
carSensors.removeCarHardwareLocationListener(locationListener)
carInfo.removeSpeedListener(speedListener)
Log.i(TAG, "In onDestroy()")
val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
@@ -73,26 +84,47 @@ class NavigationSession : Session(), NavigationScreen.Listener {
}
}
lateinit var navigationViewModel : ViewModel
lateinit var navigationViewModel: ViewModel
lateinit var baseStyle: BaseStyle.Json
val locationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
OnCarDataAvailableListener { data ->
if (data.location.status == CarValue.STATUS_SUCCESS) {
val location = data.location.value
surfaceRenderer.updateCarLocation(location!!)
}
}
val speedListener = OnCarDataAvailableListener<Speed> { data ->
if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) {
val speed = data.displaySpeedMetersPerSecond.value
surfaceRenderer.updateCarSpeed(speed!!)
}
}
init {
val lifecycle: Lifecycle = lifecycle
lifecycle.addObserver(mLifeCycleObserver)
}
override fun onCreateScreen(intent: Intent): Screen {
navigationViewModel = getRouteEngine(carContext)
routeModel = RouteCarModel()
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, baseStyle)
navigationScreen =
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED && !useContacts
== PackageManager.PERMISSION_GRANTED
&& carContext.checkSelfPermission("com.google.android.gms.permission.CAR_SPEED") == PackageManager.PERMISSION_GRANTED
&& !useContacts
|| (useContacts && carContext.checkSelfPermission(Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED)
== PackageManager.PERMISSION_GRANTED)
) {
requestLocationUpdates()
} else {
@@ -111,9 +143,23 @@ class NavigationSession : Session(), NavigationScreen.Listener {
}
)
}
addSensors()
return navigationScreen
}
fun addSensors() {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
carSensors.addCarHardwareLocationListener(
CarSensors.UPDATE_RATE_NORMAL,
carContext.mainExecutor,
locationListener
)
carInfo.addSpeedListener(carContext.mainExecutor, speedListener)
}
override fun onNewIntent(intent: Intent) {
val screenManager = carContext.getCarService(ScreenManager::class.java)
if ((CarContext.ACTION_NAVIGATE == intent.action)) {
@@ -151,6 +197,11 @@ class NavigationSession : Session(), NavigationScreen.Listener {
}
}
override fun onCarConfigurationChanged(newConfiguration: Configuration) {
println("Configuration: ${newConfiguration.isNightModeActive}")
super.onCarConfigurationChanged(newConfiguration)
}
@SuppressLint("MissingPermission")
fun requestLocationUpdates() {
val locationManager =

View File

@@ -18,8 +18,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
@@ -27,13 +25,12 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.map.DarkMode
import com.kouros.navigation.car.map.DrawNavigationImages
import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.map.rememberBaseStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.model.RouteModel
@@ -51,7 +48,8 @@ import org.maplibre.spatialk.geojson.Position
class SurfaceRenderer(
private var carContext: CarContext, lifecycle: Lifecycle,
private var routeModel: RouteCarModel
private var routeModel: RouteCarModel,
private var baseStyle: BaseStyle.Json
) : DefaultLifecycleObserver {
var lastLocation = location(0.0, 0.0)
@@ -174,12 +172,8 @@ class SurfaceRenderer(
val speedCameras: String? by speedCamerasData.observeAsState()
val paddingValues = getPaddingValues(height, viewStyle)
val cameraState = cameraState(paddingValues, position, tilt)
val baseStyle = remember {
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
}
DarkMode(carContext, baseStyle)
MapLibre(carContext, cameraState, baseStyle, route, viewStyle, speedCameras)
val rememberBaseStyle = rememberBaseStyle(baseStyle)
MapLibre(carContext, cameraState, rememberBaseStyle, route, viewStyle, speedCameras)
ShowPosition(cameraState, position, paddingValues)
}
@@ -262,7 +256,7 @@ class SurfaceRenderer(
)
lastBearing = cameraPosition.value!!.bearing
lastLocation = location
speed.value = location.speed
//speed.value = location.speed
if (!countDownTimerActive) {
countDownTimerActive = true
val mainThreadHandler = Handler(carContext.mainLooper)
@@ -331,6 +325,14 @@ class SurfaceRenderer(
)
}
fun updateCarLocation(location: Location) {
// updateLocation(location)
}
fun updateCarSpeed(newSpeed: Float) {
speed.value = newSpeed
}
fun setCategoryLocation(location: Location, category: String) {
viewStyle = ViewStyle.AMENITY_VIEW
cameraPosition.postValue(

View File

@@ -1,10 +1,8 @@
package com.kouros.navigation.car.map
import android.content.Context
import android.content.res.AssetFileDescriptor
import android.location.Location
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -13,13 +11,17 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
@@ -88,20 +90,19 @@ fun cameraState(
fun MapLibre(
context: Context,
cameraState: CameraState,
baseStyle: MutableState<BaseStyle.Uri>,
baseStyle: BaseStyle.Json,
route: String?,
viewStyle: ViewStyle,
speedCameras: String? = ""
) {
//val baseStyle = BaseStyleModel().readStyle(context)
MaplibreMap(
options = MapOptions(
ornamentOptions =
OrnamentOptions(isScaleBarEnabled = false)
),
cameraState = cameraState,
baseStyle = baseStyle.value
baseStyle = baseStyle
) {
getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
@@ -372,23 +373,14 @@ private fun MaxSpeed(
}
@Composable
fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS)
if (darkMode == 0) {
baseStyle.value = BaseStyle.Uri(Constants.STYLE)
}
if (darkMode == 1) {
baseStyle.value = BaseStyle.Uri(Constants.STYLE_DARK)
}
if (darkMode == 2) {
baseStyle.value =
(if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
Constants.STYLE
))
fun rememberBaseStyle( baseStyle : BaseStyle.Json): BaseStyle.Json {
val rememberBaseStyle by remember() {
mutableStateOf(baseStyle)
}
return rememberBaseStyle
}
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
return when (viewStyle) {
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(

View File

@@ -26,9 +26,10 @@ class RequestPermissionScreen(
override fun onGetTemplate(): Template {
val permissions: MutableList<String?> = ArrayList()
permissions.add(permission.ACCESS_FINE_LOCATION)
permissions.add("com.google.android.gms.permission.CAR_SPEED")
//permissions.add(permission.READ_CONTACTS)
val message = "This app needs access to location in order to show the map around you"
val message = "This app needs access to location and to car speed"
val listener: OnClickListener = ParkedOnlyOnClickListener.create {
carContext.requestPermissions(

View File

@@ -1,13 +1,31 @@
package com.kouros.navigation.model
import android.content.Context
import android.content.res.Configuration
import androidx.compose.foundation.isSystemInDarkTheme
import com.kouros.data.R
import org.maplibre.compose.style.BaseStyle
class BaseStyleModel {
fun readStyle(context: Context): BaseStyle.Json {
println("Read Style")
val liberty = context.resources.openRawResource(R.raw.liberty)
fun isDarkTheme(context: Context): Boolean {
return context.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
fun readStyle(context: Context, darkModeSettings: Int, isCarDarkMode: Boolean): BaseStyle.Json {
println("BaseStyle ${isDarkTheme(context)}")
val liberty = when(darkModeSettings) {
0 -> context.resources.openRawResource(R.raw.liberty)
1 -> context.resources.openRawResource(R.raw.liberty_night)
else -> {
if (isDarkTheme(context) || isCarDarkMode) {
context.resources.openRawResource(R.raw.liberty_night)
} else {
context.resources.openRawResource(R.raw.liberty)
}
}
}
val libertyString = liberty.bufferedReader().use { it.readText() }
val baseStyle = BaseStyle.Json(libertyString)
return baseStyle

View File

@@ -42,6 +42,7 @@ foundationLayout = "1.10.0"
[libraries]
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
androidx-app-projected = { module = "androidx.car.app:app-projected" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
junit = { group = "junit", name = "junit", version.ref = "junit" }