├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── android-ci.yml
├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── deploymentTargetSelector.xml
├── discord.xml
├── emulatorDisplays.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
├── runConfigurations.xml
├── studiobot.xml
└── vcs.xml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── nl
│ │ └── flitsmeister
│ │ └── maplibrecar
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── nl
│ │ │ └── flitsmeister
│ │ │ └── maplibrecar
│ │ │ ├── MainActivity.kt
│ │ │ └── ui
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ └── res
│ │ ├── values
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── nl
│ └── flitsmeister
│ └── maplibrecar
│ └── ExampleUnitTest.kt
├── automotive
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── nl
│ │ └── flitsmeister
│ │ └── maplibrecar
│ │ └── automotive
│ │ └── Util.kt
│ └── res
│ └── values
│ ├── strings.xml
│ └── themes.xml
├── build.gradle.kts
├── car_common
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── nl
│ │ └── flitsmeister
│ │ └── car_common
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── nl
│ │ │ └── flitsmeister
│ │ │ └── car_common
│ │ │ ├── CarMapContainer.kt
│ │ │ ├── CarMapRenderer.kt
│ │ │ ├── ICarMapRenderer.kt
│ │ │ ├── MyCarAppService.kt
│ │ │ ├── MyCarSession.kt
│ │ │ ├── PhonePermissionActivity.kt
│ │ │ ├── extentions
│ │ │ ├── CarBuilders+Extentions.kt
│ │ │ ├── CarContext+Extentions.kt
│ │ │ ├── Context+Extensions.kt
│ │ │ └── CoroutinesHelper.kt
│ │ │ └── screens
│ │ │ ├── CarMapScreen.kt
│ │ │ ├── CarMenuScreen.kt
│ │ │ └── CarPermissionScreen.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_menu.xml
│ │ ├── ic_zoom_in.png
│ │ └── ic_zoom_out.png
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── car.xml
│ │ ├── colors.xml
│ │ ├── ic_launcher_background.xml
│ │ └── strings.xml
│ │ └── xml
│ │ └── automotive_app_desc.xml
│ └── test
│ └── java
│ └── nl
│ └── flitsmeister
│ └── car_common
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
└── screenshot1.png
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [maplibre]
2 | open_collective: maplibre
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | Check if applicable:
2 | - [ ] There's a build error (compile time)
3 | - [ ] The app crashes (runtime)
4 | - [x] This issue is related to Android Auto
5 | - [x] This issue is related to Android Automotive
6 | - [ ] This can be reproduced in the emulator (Android emulator and/or Desktop Headunit Emulator)
7 | - [ ] This can be reproduced in a real device (real phone and real car)
8 |
9 | Steps to trigger behavior:
10 | --------------------------
11 | //TODO
12 |
13 | Screenshot/Recording (if applicable):
14 | ------------------------------------
15 | //TODO
16 |
17 | Expected behavior:
18 | ------------------
19 | //TODO
20 |
21 | Actual behavior:
22 | ----------------
23 | //TODO
24 |
25 | Additional context (if applicable):
26 | -------------------
27 | //TODO
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | User story
2 | ----------
3 | //TODO
4 |
11 |
12 | Rationale
13 | ---------
14 | //TODO
15 |
20 |
21 | Impact
22 | ------
23 | //TODO
24 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Changes in this PR
4 | ------------------
5 | //TODO
6 |
7 | Checklist
8 | ---------
9 |
10 | - [ ] Briefly describe the changes in this PR.
11 | - [ ] Link to related issues, if applicable.
12 | - [ ] Include before/after visuals or gifs if this PR includes visual changes.
13 | - [ ] Changes have been tested (using Android emulator or real hardware)
14 | - [ ] There are no failing unit tests
15 | - [ ] CI (Github actions) has run, and didn't return an error
16 | - [ ] Another developer has approved this PR
17 |
--------------------------------------------------------------------------------
/.github/workflows/android-ci.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | # run on pushes to the main branch
6 | branches:
7 | - main
8 |
9 | pull_request:
10 | # run on all pr's regardless of branch name
11 | branches:
12 | - "*"
13 |
14 | concurrency:
15 | # cancel jobs on PRs only
16 | group: ${{ github.workflow }}-${{ github.ref }}
17 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
18 |
19 | jobs:
20 | build:
21 |
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - uses: actions/checkout@v4
26 |
27 | - name: set up JDK 11
28 | uses: actions/setup-java@v4
29 | with:
30 | java-version: '17'
31 | distribution: 'temurin'
32 | cache: gradle
33 |
34 | - name: Grant execute permission for gradlew
35 | run: chmod +x gradlew
36 | - name: Build with Gradle
37 | run: ./gradlew build
38 | - name: Run tests
39 | run: ./gradlew test --stacktrace
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/emulatorDisplays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
68 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/studiobot.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant
2 | [](https://github.com/maplibre/maplibre/blob/main/CODE_OF_CONDUCT.md)
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We welcome contributions to this project.
4 | Feel free to open an issue or create a pull request.
5 |
6 | Ideas can be discussed on the OSM-US Slack, in the #maplibre-android channel.
7 | Invite link: https://slack.openstreetmap.us
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Flitsmeister
4 | Copyright (c) 2025 MapLibre contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MapLibre Sample app for Android Auto and Automotive
2 | ===================================================
3 |
4 | This sample app will demonstrate how to use MapLibre on Android Auto and Android Automotive.
5 | (Also known as AAOS, Android for Cars)
6 |
7 | 
8 |
9 | So how does this work?
10 | ----------------------
11 | Android Auto uses a Template system. It will not allow you to use regular views.
12 | MapLibre will provide the map as a View. If we want to use this MapView on Android Auto, we have to render it on the provided Surface.
13 | See also: https://developer.android.com/training/cars/apps#declare-surface-permission
14 |
15 | To get the MapView to render on the surface, we do the following;
16 | We render the MapView offscreen, create a 'screenshot' of this view.
17 | And we'll draw this Bitmap to the Surface via a Canvas.
18 | Repeat the process 30 times per second, and you've got a working MapView on Android Auto.
19 |
20 | This process is not very efficient, but this is what we got to work with.
21 | Maybe in the future, we can find a solution to make MapLibre draw directly unto the Surface,
22 | and thereby skip the workaround. Which is drawing a Bitmap on a Canvas.
23 |
24 |
25 | How is the code structured?
26 | ---------------------------
27 | The map can be drawn on Android Auto, and Android Automotive. A lot of code (most of it), can be shared between the two.
28 | Therefore, this bulk of the code is in a shared module, called `car_common`. This is used by the `app` (Smartphone app, uses Android Auto) and the `automotive` module.
29 | The `automotive` module actually has no code at all, just some config and resources. Everything it uses comes from the `car_common` module.
30 |
31 | ```mermaid
32 | flowchart TD
33 | A[car_common]
34 | B[app]
35 | C[automotive]
36 |
37 | B --> A
38 | C --> A
39 | ```
40 |
41 |
42 | Enough words; Show me the code!
43 | -------------------------------
44 |
45 | We have a class called `CarMapContainer`, which will take care of the MapView code;
46 | This is to create the MapView, enable texture mode, and add to the WindowManager.
47 | This is needed to ensure the MapView will render, and we can get the Texture later for rendering it onto the Canvas.
48 | ```kotlin
49 | val mapView = MapView(carContext, MapLibreMapOptions.createFromAttributes(carContext).apply {
50 | // Set the textureMode to true, so a TextureView is created
51 | // We can extract this TextureView to draw on the Android Auto surface
52 | textureMode(true)
53 | }).apply {
54 | setLayerType(View.LAYER_TYPE_HARDWARE, Paint())
55 | }
56 | //(...)
57 | carContext.windowManager.addView(
58 | mapView /* the MapView */,
59 | getWindowManagerLayoutParams()
60 | )
61 | ```
62 |
63 | Furthermore, we have a class called `CarMapRenderer`, which will render the Map on the provided Surface.
64 | ```kotlin
65 | //Make sure to get the Surface by registering as SurfaceCallback
66 | override fun onCreate(owner: LifecycleOwner) {
67 | super.onCreate(owner)
68 | try {
69 | carContext.appManager.setSurfaceCallback(this) //Make sure we get the Surface from Android Auto
70 | } catch (e: Exception) {
71 | Log.e(LOG_TAG, "Could not set surface callback", e)
72 | return
73 | }
74 | }
75 |
76 | //In onSurfaceAvailable, we get the Surface onto which we need to draw the map
77 |
78 | override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
79 | Log.v(LOG_TAG, "CarMapRenderer.onSurfaceAvailable")
80 | this.surfaceContainer = surfaceContainer
81 | mapContainer.setSurfaceSize(surfaceContainer.width, surfaceContainer.height)
82 |
83 | // // Start rendering the map on the Android Auto surface when any behavior changes are detected in the map.
84 | mapContainer.mapViewInstance?.apply {
85 | addOnDidBecomeIdleListener { drawOnSurface() }
86 | addOnWillStartRenderingFrameListener {
87 | drawOnSurface()
88 | }
89 | }
90 | runOnMainThread {
91 | // Start drawing the map on the android auto surface
92 | drawOnSurface()
93 | }
94 | }
95 |
96 | //Using a canvas, draw the map
97 | private fun drawOnSurface() {
98 | val mapView = mapContainer.mapViewInstance ?: return
99 | val surface = surfaceContainer?.surface ?: return
100 | val canvas = surface.lockHardwareCanvas()
101 | drawMapOnCanvas(mapView, canvas)
102 | surface.unlockCanvasAndPost(canvas)
103 | }
104 |
105 | //The actual drawing of the map
106 | private fun drawMapOnCanvas(mapView: MapView, canvas: Canvas) {
107 | val mapViewTextureView = mapView.takeIf { it.childCount > 0 }?.getChildAt(0) as? TextureView
108 | mapViewTextureView?.bitmap?.let {
109 | canvas.drawBitmap(it, 0f, 0f, null)
110 | }
111 | }
112 |
113 | ```
114 |
115 | Other MapTiles / Style
116 | ----------------------
117 |
118 | You can fix this by setting your own Style.
119 | Set this in the following places:
120 |
121 | Set your own Style in: `CarMapContainer.kt` (car_common);
122 | ```kotlin
123 | getMapAsync {
124 | mapViewInstance = this
125 | mapLibreMapInstance = it
126 | it.setStyle(
127 | //TODO: Set your own style here
128 | Style.Builder().fromUri("https://demotiles.maplibre.org/style.json")
129 | )
130 | }
131 | ```
132 | And in `MainActivity` (app);
133 | ```kotlin
134 | private fun initMap(map: MapLibreMap) {
135 | try {
136 | map.setStyle(
137 | //TODO: Set your own style here!
138 | Style.Builder().fromUri("https://demotiles.maplibre.org/style.json")
139 | )
140 | } catch (e: Exception) {
141 | Log.e("MapLibreCar", "Error setting local style", e)
142 | }
143 | }
144 | ```
145 |
146 | Main MapLibre website
147 | ---------------------
148 | https://maplibre.org/
149 |
150 | Discussing
151 | ----------
152 | This project can be discussed on the OSM-US Slack, in channel #maplibre-android, invite link: https://slack.openstreetmap.us/
153 |
154 | Contributing
155 | ------------
156 | We welcome contributions to this project. Please read the [Contributing Guidelines](CONTRIBUTING.md) for more information.
157 |
158 | License
159 | -------
160 | [MIT](LICENSE)
161 |
162 | Contributor
163 | --------
164 | [Flitsmeister](https://flitsmeister.nl)
165 |
166 | [MapLibre](https://maplibre.org)
167 |
168 |
169 |
170 |
171 |
172 | Copyright (c) 2025 MapLibre contributors
173 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | For an up-to-date policy refer to
2 | https://github.com/maplibre/maplibre/blob/main/SECURITY.md
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | }
6 |
7 | android {
8 | namespace = "nl.flitsmeister.maplibrecar"
9 | compileSdk = 35
10 |
11 | defaultConfig {
12 | applicationId = "nl.flitsmeister.maplibrecar"
13 | minSdk = 29
14 | targetSdk = 35
15 | versionCode = 1
16 | versionName = "1.0"
17 |
18 | buildConfigField("String", "APP_PLATFORM", "\"ANDROID_AUTO\"")
19 | addManifestPlaceholders(
20 | mapOf("IS_DEBUG" to "TRUE",)
21 | )
22 |
23 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
24 | }
25 |
26 | buildTypes {
27 | release {
28 | isMinifyEnabled = false
29 | proguardFiles(
30 | getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
31 | )
32 |
33 | addManifestPlaceholders(
34 | mapOf("IS_DEBUG" to "FALSE",)
35 | )
36 | }
37 | }
38 | compileOptions {
39 | sourceCompatibility = JavaVersion.VERSION_11
40 | targetCompatibility = JavaVersion.VERSION_11
41 | }
42 | kotlinOptions {
43 | jvmTarget = "11"
44 | }
45 | buildFeatures {
46 | compose = true
47 | buildConfig = true
48 | }
49 | }
50 |
51 | dependencies {
52 |
53 | implementation(project(":car_common"))
54 |
55 | implementation(libs.maplibre.sdk)
56 | implementation(libs.maplibre.nav)
57 |
58 | implementation(libs.androidx.core.ktx)
59 | implementation(libs.androidx.lifecycle.runtime.ktx)
60 | implementation(libs.androidx.activity.compose)
61 | implementation(platform(libs.androidx.compose.bom))
62 | implementation(libs.androidx.ui)
63 | implementation(libs.androidx.ui.graphics)
64 | implementation(libs.androidx.ui.tooling.preview)
65 | implementation(libs.androidx.material3)
66 | testImplementation(libs.junit)
67 | androidTestImplementation(libs.androidx.junit)
68 | androidTestImplementation(libs.androidx.espresso.core)
69 | androidTestImplementation(platform(libs.androidx.compose.bom))
70 | androidTestImplementation(libs.androidx.ui.test.junit4)
71 | debugImplementation(libs.androidx.ui.tooling)
72 | debugImplementation(libs.androidx.ui.test.manifest)
73 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/nl/flitsmeister/maplibrecar/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("nl.flitsmeister.maplibrecar", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/nl/flitsmeister/maplibrecar/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.material3.Scaffold
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.viewinterop.AndroidView
13 | import nl.flitsmeister.car_common.R
14 | import nl.flitsmeister.maplibrecar.ui.theme.MapLibreCarTheme
15 | import org.maplibre.android.MapLibre
16 | import org.maplibre.android.camera.CameraPosition
17 | import org.maplibre.android.geometry.LatLng
18 | import org.maplibre.android.maps.MapLibreMap
19 | import org.maplibre.android.maps.MapLibreMapOptions
20 | import org.maplibre.android.maps.MapView
21 | import org.maplibre.android.maps.Style
22 |
23 | class MainActivity : ComponentActivity() {
24 |
25 | var mapView: MapView? = null
26 | var mapLibreMap: MapLibreMap? = null
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | enableEdgeToEdge()
31 | setContent {
32 | MapLibreCarTheme {
33 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
34 | AndroidView(modifier = Modifier.padding(innerPadding), factory = { context ->
35 | //TextView(context).apply { setText("Hello MapLibreCar") }
36 | val mapLibreMapOptions = MapLibreMapOptions.createFromAttributes(context).apply {
37 | textureMode(true)
38 | camera(
39 | CameraPosition.Builder()
40 | .zoom(2.0)
41 | .target(LatLng(48.507879, 8.363795))
42 | .build()
43 | )
44 | }
45 | MapLibre.getInstance(context)
46 | mapView = MapView(context, mapLibreMapOptions)
47 | mapView?.onCreate(savedInstanceState)
48 | mapView?.getMapAsync {
49 | mapLibreMap = it
50 | initMap(it)
51 | }
52 | mapView!!
53 | })
54 | }
55 | }
56 | }
57 | }
58 |
59 | private fun initMap(map: MapLibreMap) {
60 | try {
61 | map.setStyle(
62 | //TODO: Set your own style here!
63 | Style.Builder().fromUri("https://demotiles.maplibre.org/style.json")
64 | )
65 | } catch (e: Exception) {
66 | Log.e("MapLibreCar", "Error setting local style", e)
67 | }
68 | }
69 |
70 | override fun onStart() {
71 | super.onStart()
72 | mapView?.onStart()
73 | }
74 |
75 | override fun onResume() {
76 | super.onResume()
77 | mapView?.onResume()
78 | }
79 |
80 | override fun onPause() {
81 | super.onPause()
82 | mapView?.onPause()
83 | }
84 |
85 | override fun onStop() {
86 | super.onStop()
87 | mapView?.onStop()
88 | }
89 |
90 | override fun onSaveInstanceState(outState: Bundle) {
91 | super.onSaveInstanceState(outState)
92 | mapView?.onSaveInstanceState(outState)
93 | }
94 |
95 | override fun onDestroy() {
96 | super.onDestroy()
97 | mapView?.onDestroy()
98 | }
99 |
100 | @Deprecated("Deprecated in Java")
101 | override fun onLowMemory() {
102 | super.onLowMemory()
103 | mapView?.onLowMemory()
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/nl/flitsmeister/maplibrecar/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/app/src/main/java/nl/flitsmeister/maplibrecar/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.platform.LocalContext
13 |
14 | private val DarkColorScheme = darkColorScheme(
15 | primary = Purple80,
16 | secondary = PurpleGrey80,
17 | tertiary = Pink80
18 | )
19 |
20 | private val LightColorScheme = lightColorScheme(
21 | primary = Purple40,
22 | secondary = PurpleGrey40,
23 | tertiary = Pink40
24 |
25 | /* Other default colors to override
26 | background = Color(0xFFFFFBFE),
27 | surface = Color(0xFFFFFBFE),
28 | onPrimary = Color.White,
29 | onSecondary = Color.White,
30 | onTertiary = Color.White,
31 | onBackground = Color(0xFF1C1B1F),
32 | onSurface = Color(0xFF1C1B1F),
33 | */
34 | )
35 |
36 | @Composable
37 | fun MapLibreCarTheme(
38 | darkTheme: Boolean = isSystemInDarkTheme(),
39 | // Dynamic color is available on Android 12+
40 | dynamicColor: Boolean = true,
41 | content: @Composable () -> Unit
42 | ) {
43 | val colorScheme = when {
44 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
45 | val context = LocalContext.current
46 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
47 | }
48 |
49 | darkTheme -> DarkColorScheme
50 | else -> LightColorScheme
51 | }
52 |
53 | MaterialTheme(
54 | colorScheme = colorScheme,
55 | typography = Typography,
56 | content = content
57 | )
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/nl/flitsmeister/maplibrecar/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/nl/flitsmeister/maplibrecar/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/automotive/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/automotive/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "nl.flitsmeister.maplibrecar.automotive"
8 | compileSdk = 35
9 |
10 | defaultConfig {
11 | applicationId = "nl.flitsmeister.maplibrecar.automotive"
12 | minSdk = 29
13 | targetSdk = 35
14 | versionCode = 1
15 | versionName = "1.0"
16 |
17 | buildConfigField("String", "APP_PLATFORM", "\"AAOS\"")
18 | addManifestPlaceholders(
19 | mapOf("IS_DEBUG" to "TRUE")
20 | )
21 |
22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
23 | }
24 |
25 | buildTypes {
26 | release {
27 | isMinifyEnabled = false
28 | proguardFiles(
29 | getDefaultProguardFile("proguard-android-optimize.txt"),
30 | "proguard-rules.pro"
31 | )
32 | addManifestPlaceholders(
33 | mapOf("IS_DEBUG" to "FALSE")
34 | )
35 | }
36 | }
37 | compileOptions {
38 | sourceCompatibility = JavaVersion.VERSION_11
39 | targetCompatibility = JavaVersion.VERSION_11
40 | }
41 | kotlinOptions {
42 | jvmTarget = "11"
43 | }
44 | buildFeatures {
45 | buildConfig = true
46 | }
47 | }
48 |
49 | dependencies {
50 | implementation(project(":car_common"))
51 | implementation(libs.car.app.automotive)
52 | implementation(libs.maplibre.sdk)
53 | implementation(libs.maplibre.nav)
54 |
55 | implementation(libs.androidx.core.ktx)
56 | implementation(libs.androidx.appcompat)
57 | testImplementation(libs.junit)
58 | androidTestImplementation(libs.androidx.junit)
59 | androidTestImplementation(libs.androidx.espresso.core)
60 | }
--------------------------------------------------------------------------------
/automotive/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/automotive/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
14 |
17 |
20 |
23 |
24 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
60 |
61 |
64 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/automotive/src/main/java/nl/flitsmeister/maplibrecar/automotive/Util.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.maplibrecar.automotive
2 |
3 | //This file has been left intentionally blank
4 | //The automotive module uses no code, other than what's in the car_common module.
--------------------------------------------------------------------------------
/automotive/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MapLibreAutomotive
3 |
--------------------------------------------------------------------------------
/automotive/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | alias(libs.plugins.kotlin.compose) apply false
6 | alias(libs.plugins.android.library) apply false
7 | }
--------------------------------------------------------------------------------
/car_common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/car_common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "nl.flitsmeister.car_common"
8 | compileSdk = 34
9 |
10 | defaultConfig {
11 | minSdk = 29
12 |
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles("consumer-rules.pro")
15 | }
16 |
17 | buildTypes {
18 | release {
19 | isMinifyEnabled = false
20 | proguardFiles(
21 | getDefaultProguardFile("proguard-android-optimize.txt"),
22 | "proguard-rules.pro"
23 | )
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_11
28 | targetCompatibility = JavaVersion.VERSION_11
29 | }
30 | kotlinOptions {
31 | jvmTarget = "11"
32 | }
33 | }
34 |
35 | dependencies {
36 | //Android Auto
37 | implementation(libs.car.app)
38 | implementation(libs.maplibre.sdk)
39 | implementation(libs.maplibre.nav)
40 |
41 | implementation(libs.androidx.core.ktx)
42 | implementation(libs.androidx.appcompat)
43 | implementation(libs.material)
44 | testImplementation(libs.junit)
45 | androidTestImplementation(libs.androidx.junit)
46 | androidTestImplementation(libs.androidx.espresso.core)
47 | }
--------------------------------------------------------------------------------
/car_common/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/consumer-rules.pro
--------------------------------------------------------------------------------
/car_common/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/car_common/src/androidTest/java/nl/flitsmeister/car_common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("nl.flitsmeister.car_common.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/car_common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
36 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/car_common/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/CarMapContainer.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import android.animation.Animator
4 | import android.animation.ValueAnimator
5 | import android.graphics.Paint
6 | import android.graphics.PixelFormat
7 | import android.graphics.PointF
8 | import android.util.Log
9 | import android.view.View
10 | import android.view.WindowManager
11 | import android.view.animation.DecelerateInterpolator
12 | import androidx.car.app.CarContext
13 | import androidx.lifecycle.DefaultLifecycleObserver
14 | import androidx.lifecycle.Lifecycle
15 | import androidx.lifecycle.LifecycleOwner
16 | import nl.flitsmeister.car_common.extentions.runOnMainThread
17 | import nl.flitsmeister.car_common.extentions.windowManager
18 | import org.maplibre.android.MapLibre
19 | import org.maplibre.android.constants.MapLibreConstants
20 | import org.maplibre.android.maps.MapLibreMap
21 | import org.maplibre.android.maps.MapLibreMapOptions
22 | import org.maplibre.android.maps.MapView
23 | import org.maplibre.android.maps.Style
24 | import kotlin.math.ln
25 |
26 | class CarMapContainer(
27 | private val carContext: CarContext, lifecycle: Lifecycle
28 | ) : DefaultLifecycleObserver {
29 |
30 | init {
31 | lifecycle.addObserver(this)
32 | }
33 |
34 | var mapViewInstance: MapView? = null
35 | private set
36 |
37 | var mapLibreMapInstance: MapLibreMap? = null
38 |
39 | var surfaceWidth: Int? = null
40 | var surfaceHeight: Int? = null
41 |
42 | private var scaleAnimator: Animator? = null
43 |
44 |
45 | fun scrollBy(x: Float, y: Float) {
46 | mapLibreMapInstance?.scrollBy(-x, -y, 0)
47 | }
48 |
49 | private fun createScaleAnimator(
50 | currentZoom: Double,
51 | zoomAddition: Double,
52 | animationFocalPoint: PointF?,
53 | ): Animator {
54 | val animator =
55 | ValueAnimator.ofFloat(currentZoom.toFloat(), (currentZoom + zoomAddition).toFloat())
56 | animator.apply {
57 | duration = MapLibreConstants.ANIMATION_DURATION.toLong()
58 | interpolator = DecelerateInterpolator()
59 | addUpdateListener { animation ->
60 | animationFocalPoint?.let {
61 | mapLibreMapInstance?.setZoom(
62 | (animation.animatedValue as Float).toDouble(),
63 | it, 0
64 | )
65 | }
66 | }
67 | }
68 | return animator
69 | }
70 |
71 | private fun doubleClickZoomWithAnimation(zoomFocalPoint: PointF?, isZoomIn: Boolean) {
72 | cancelCurrentAnimator(scaleAnimator)
73 | val currentZoom = mapLibreMapInstance?.zoom
74 | currentZoom?.let {
75 | scaleAnimator = createScaleAnimator(
76 | it,
77 | if (isZoomIn) 1.0 else -1.0,
78 | zoomFocalPoint
79 | )
80 | scaleAnimator?.start()
81 | }
82 | }
83 |
84 | private fun cancelCurrentAnimator(animator: Animator?) {
85 | if (animator != null && animator.isStarted) {
86 | animator.cancel()
87 | }
88 | }
89 |
90 | fun onScale(focusX: Float, focusY: Float, scaleFactor: Float) {
91 | if (scaleFactor == DOUBLE_CLICK_FACTOR) {
92 | doubleClickZoomWithAnimation(PointF(focusX, focusY), true)
93 | return
94 | }
95 | if (scaleFactor == -DOUBLE_CLICK_FACTOR) {
96 | doubleClickZoomWithAnimation(PointF(focusX, focusY), false)
97 | return
98 | }
99 | val currentZoomLevel = mapLibreMapInstance?.zoom
100 |
101 | // Calculate the additional zoom level based on the scale factor.
102 | val zoomAdditional =
103 | (ln(
104 | scaleFactor.toDouble()
105 | ) / ln(Math.PI / 2)) * MapLibreConstants.ZOOM_RATE
106 |
107 | currentZoomLevel?.let {
108 | mapLibreMapInstance?.setZoom(it + zoomAdditional, PointF(focusX, focusY), 0)
109 | }
110 | }
111 |
112 | /**
113 | * This function is called when the surface is created, to update the mapview with the surface sizes
114 | */
115 | fun setSurfaceSize(surfaceWidth: Int, surfaceHeight: Int) {
116 | Log.v(LOG_TAG, "setSurfaceSize: $surfaceWidth, $surfaceHeight")
117 | if (this.surfaceWidth != surfaceWidth || this.surfaceHeight != surfaceHeight) {
118 | this.surfaceWidth = surfaceWidth
119 | this.surfaceHeight = surfaceHeight
120 | mapViewInstance?.apply {
121 | carContext.windowManager.updateViewLayout(this, getWindowManagerLayoutParams())
122 | }
123 | }
124 | }
125 |
126 | override fun onCreate(owner: LifecycleOwner) {
127 | MapLibre.getInstance(carContext)
128 |
129 | runOnMainThread {
130 | mapViewInstance = createMapViewInstance().apply {
131 | // Add the mapView to a window using the windowManager. This is needed for the mapView to start rendering.
132 | // The mapView is not actually shown on any screen, but acts as though it is visible.
133 | carContext.windowManager.addView(
134 | this,
135 | getWindowManagerLayoutParams()
136 | )
137 | onStart()
138 | getMapAsync {
139 | mapViewInstance = this
140 | mapLibreMapInstance = it
141 | it.setStyle(
142 | //TODO: Set your own style here
143 | Style.Builder().fromUri("https://demotiles.maplibre.org/style.json")
144 | )
145 | }
146 | }
147 | }
148 | }
149 |
150 | private fun getWindowManagerLayoutParams() = WindowManager.LayoutParams(
151 | surfaceWidth ?: WindowManager.LayoutParams.MATCH_PARENT,
152 | surfaceHeight ?: WindowManager.LayoutParams.MATCH_PARENT,
153 | WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION,
154 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
155 | PixelFormat.RGBX_8888
156 | )
157 |
158 | override fun onDestroy(owner: LifecycleOwner) {
159 | runOnMainThread {
160 | mapLibreMapInstance = null
161 |
162 | mapViewInstance?.run {
163 | onStop()
164 | onDestroy()
165 | carContext.windowManager.removeView(this)
166 | }
167 | mapViewInstance = null
168 | }
169 | }
170 |
171 | private fun createMapViewInstance() =
172 | MapView(carContext, MapLibreMapOptions.createFromAttributes(carContext).apply {
173 | // Set the textureMode to true, so a TextureView is created
174 | // We can extract this TextureView to draw on the Android Auto surface
175 | textureMode(true)
176 |
177 | }).apply {
178 | setLayerType(View.LAYER_TYPE_HARDWARE, Paint())
179 | }
180 |
181 | companion object {
182 | const val LOG_TAG = "CarMapContainer"
183 | const val DOUBLE_CLICK_FACTOR = 2.0F
184 | }
185 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/CarMapRenderer.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 | import android.graphics.PointF
6 | import android.graphics.Rect
7 | import android.graphics.Typeface
8 | import android.os.Handler
9 | import android.os.Looper
10 | import android.util.Log
11 | import android.view.MotionEvent
12 | import android.view.TextureView
13 | import android.view.View
14 | import androidx.car.app.CarContext
15 | import androidx.car.app.SurfaceCallback
16 | import androidx.car.app.SurfaceContainer
17 | import androidx.lifecycle.DefaultLifecycleObserver
18 | import androidx.lifecycle.Lifecycle
19 | import androidx.lifecycle.LifecycleOwner
20 | import nl.flitsmeister.car_common.extentions.appManager
21 | import nl.flitsmeister.car_common.extentions.runOnMainThread
22 | import org.maplibre.android.maps.MapView
23 |
24 | class CarMapRenderer(
25 | private val carContext: CarContext,
26 | serviceLifecycle: Lifecycle
27 | ) : SurfaceCallback, DefaultLifecycleObserver, ICarMapRenderer {
28 |
29 | // The map container used to handle the map lifecycle
30 | private val mapContainer = CarMapContainer(carContext, serviceLifecycle)
31 |
32 | private val osmPaint = Paint().apply {
33 | color = carContext.getColor(R.color.osm_attribution)
34 | textAlign = Paint.Align.RIGHT
35 | typeface = Typeface.DEFAULT
36 | }
37 |
38 | // The surface to draw the map container on
39 | private var surfaceContainer: SurfaceContainer? = null
40 |
41 | // Handler to post actions to the UI thread
42 | private val uiHandler = Handler(Looper.getMainLooper())
43 |
44 | // The last known stable area
45 | private var lastKnownStableArea = Rect()
46 | private var lastKnownVisibleArea = Rect()
47 |
48 | init {
49 | serviceLifecycle.addObserver(this)
50 | }
51 |
52 | override fun onCreate(owner: LifecycleOwner) {
53 | super.onCreate(owner)
54 | try {
55 | carContext.appManager.setSurfaceCallback(this)
56 | } catch (e: Exception) {
57 | Log.e(LOG_TAG, "Could not set surface callback", e)
58 | return
59 | }
60 | }
61 |
62 | override fun onDestroy(owner: LifecycleOwner) {
63 | Log.v(LOG_TAG, "CarMapRenderer.onDestroy")
64 | surfaceContainer = null
65 | uiHandler.removeCallbacksAndMessages(null)
66 | try {
67 | carContext.appManager.setSurfaceCallback(null)
68 | } catch (e: Exception) {
69 | Log.e(LOG_TAG, "Could not remove surface callback", e)
70 | }
71 | }
72 |
73 | override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
74 | Log.v(LOG_TAG, "CarMapRenderer.onSurfaceAvailable")
75 | this.surfaceContainer = surfaceContainer
76 | mapContainer.setSurfaceSize(surfaceContainer.width, surfaceContainer.height)
77 | mapContainer.mapViewInstance?.apply {
78 | addOnDidBecomeIdleListener { drawOnSurface() }
79 | addOnWillStartRenderingFrameListener {
80 | drawOnSurface()
81 | }
82 | }
83 | runOnMainThread {
84 | // Start drawing the map on the android auto surface
85 | drawOnSurface()
86 | }
87 | }
88 |
89 | private fun drawOnSurface() {
90 | val mapView = mapContainer.mapViewInstance ?: return
91 | val surface = surfaceContainer?.surface ?: return
92 |
93 | val canvas = surface.lockHardwareCanvas()
94 | drawMapOnCanvas(mapView, canvas)
95 | surface.unlockCanvasAndPost(canvas)
96 | }
97 |
98 | private fun drawMapOnCanvas(mapView: MapView, canvas: Canvas) {
99 | val mapViewTextureView = mapView.takeIf { it.childCount > 0 }?.getChildAt(0) as? TextureView
100 |
101 | mapViewTextureView?.bitmap?.let {
102 | canvas.drawBitmap(it, 0f, 0f, null)
103 | }
104 |
105 | val density = carContext.resources.displayMetrics.density
106 |
107 | canvas.drawText(
108 | carContext.getString(R.string.copyright_openstreetmap),
109 | canvas.width - (12 * density),
110 | canvas.height - (4 * density),
111 | osmPaint.apply {
112 | textSize = 12 * density
113 | }
114 | )
115 | }
116 |
117 | override fun onVisibleAreaChanged(visibleArea: Rect) {
118 | if (visibleArea != lastKnownVisibleArea) {
119 | Log.v(
120 | LOG_TAG,
121 | "onVisibleAreaChanged left(${visibleArea.left}) top(${visibleArea.top}) right(${visibleArea.right}) bottom(${visibleArea.bottom})"
122 | )
123 | lastKnownVisibleArea = visibleArea
124 | }
125 | }
126 |
127 | override fun onStableAreaChanged(stableArea: Rect) {
128 | if (stableArea != lastKnownStableArea) {
129 | Log.v(
130 | LOG_TAG,
131 | "onStableAreaChanged left(${stableArea.left}) top(${stableArea.top}) right(${stableArea.right}) bottom(${stableArea.bottom})"
132 | )
133 | //if only the vertical space has changed, you can ignore this mostly.
134 | lastKnownStableArea = stableArea
135 | }
136 | }
137 |
138 | override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
139 | Log.v(LOG_TAG, "Surface destroyed")
140 | this.surfaceContainer = null
141 | uiHandler.removeCallbacksAndMessages(null)
142 | }
143 |
144 | override fun zoomInFromButton() {
145 | val centerX = surfaceContainer?.width?.toFloat()?.div(2) ?: -1f
146 | val centerY = surfaceContainer?.height?.toFloat()?.div(2) ?: -1f
147 | onScale(centerX, centerY, CarMapContainer.DOUBLE_CLICK_FACTOR)
148 | }
149 |
150 | override fun zoomOutFromButton() {
151 | val centerX = surfaceContainer?.width?.toFloat()?.div(2) ?: -1f
152 | val centerY = surfaceContainer?.height?.toFloat()?.div(2) ?: -1f
153 | onScale(centerX, centerY, -CarMapContainer.DOUBLE_CLICK_FACTOR)
154 | }
155 |
156 | //Map interactivity
157 | override fun onScale(focusX: Float, focusY: Float, scaleFactor: Float) {
158 | mapContainer.onScale(focusX, focusY, scaleFactor)
159 | }
160 |
161 | @Synchronized
162 | override fun onScroll(distanceX: Float, distanceY: Float) {
163 | Log.v(LOG_TAG, "onScroll distanceX($distanceX) distanceY($distanceY)")
164 | mapContainer.scrollBy(distanceX, distanceY)
165 | }
166 |
167 | override fun onClick(x: Float, y: Float) {
168 | super.onClick(x, y)
169 | }
170 |
171 | override fun onFling(velocityX: Float, velocityY: Float) {
172 | super.onFling(velocityX, velocityY)
173 | // We don't need to implement onFling since the MapView does this for us
174 | }
175 |
176 | companion object {
177 | const val LOG_TAG = "CarMapRenderer"
178 | }
179 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/ICarMapRenderer.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | interface ICarMapRenderer {
4 | fun zoomInFromButton()
5 | fun zoomOutFromButton()
6 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/MyCarAppService.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageManager
5 | import android.media.ApplicationMediaCapabilities
6 | import android.util.Log
7 | import androidx.car.app.CarAppService
8 | import androidx.car.app.CarContext
9 | import androidx.car.app.Session
10 | import androidx.car.app.validation.HostValidator
11 |
12 | class MyCarAppService : CarAppService() {
13 |
14 | override fun onCreateSession(): Session {
15 | return MyCarSession()
16 | }
17 |
18 | override fun createHostValidator(): HostValidator {
19 | checkManifestForDebug(this)
20 | return if (isDebug) HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
21 | else HostValidator.Builder(this)
22 | .addAllowedHosts(R.array.car_template_hosts_allowlist)
23 | .build()
24 | }
25 |
26 | companion object {
27 | const val LOG_TAG = "CarAppService"
28 | var isDebug = false
29 | var appPlatform = "ANDROID_AUTO"
30 |
31 | fun checkManifestForDebug(context: Context) {
32 | try {
33 | val app = context.packageManager.getApplicationInfo(
34 | context.packageName,
35 | PackageManager.GET_META_DATA
36 | )
37 | val bundle = app.metaData
38 | isDebug = bundle.getBoolean("nl.flitsmeister.maplibrecar.IS_DEBUG") == true
39 | appPlatform =
40 | bundle.getString("nl.flitsmeister.maplibrecar.APP_PLATFORM") ?: "ANDROID_AUTO"
41 | } catch (e: Exception) {
42 | Log.e(LOG_TAG, "Failed to check manifest (for debug mode and app platform)", e)
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/MyCarSession.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import android.content.Intent
4 | import android.content.pm.PackageManager
5 | import android.content.res.Configuration
6 | import android.util.Log
7 | import androidx.car.app.CarContext
8 | import androidx.car.app.CarToast
9 | import androidx.car.app.Screen
10 | import androidx.car.app.Session
11 | import androidx.core.content.ContextCompat
12 | import nl.flitsmeister.car_common.extentions.screenManager
13 | import nl.flitsmeister.car_common.screens.CarMapScreen
14 | import nl.flitsmeister.car_common.screens.CarPermissionScreen
15 |
16 | class MyCarSession : Session() {
17 | private lateinit var carMapRenderer: CarMapRenderer
18 | private var carConfiguration: Configuration? = null
19 |
20 | override fun onCreateScreen(intent: Intent): Screen {
21 | Log.v(LOG_TAG, "onCreateScreen: $intent")
22 | carMapRenderer = CarMapRenderer(carContext, lifecycle)
23 |
24 | val carMapScreen = CarMapScreen(carContext, carMapRenderer)
25 | carContext.screenManager.push(carMapScreen)
26 |
27 | //if location permission is not granted; Add the permission screen unto the stack
28 | if (ContextCompat.checkSelfPermission(
29 | carContext,
30 | android.Manifest.permission.ACCESS_FINE_LOCATION
31 | ) != PackageManager.PERMISSION_GRANTED
32 | ) {
33 | Log.v(LOG_TAG, "onCreateScreen: Location permission not granted")
34 | val carPermissionScreen = CarPermissionScreen(carContext)
35 | carContext.screenManager.push(carPermissionScreen)
36 | return carPermissionScreen
37 | } else {
38 | return carMapScreen
39 | }
40 | }
41 |
42 | override fun onCarConfigurationChanged(newConfiguration: Configuration) {
43 | Log.v(LOG_TAG, "onCarConfigurationChanged: old: $carConfiguration, new: $newConfiguration")
44 | carConfiguration = newConfiguration
45 | }
46 |
47 | override fun onNewIntent(intent: Intent) {
48 | Log.v(LOG_TAG, "onNewIntent: $intent")
49 | super.onNewIntent(intent)
50 |
51 | when (intent.action) {
52 | CarContext.ACTION_NAVIGATE -> {
53 | // When user speaks "navigate to X" to Google Assistant, this action will be triggered
54 | navigateFromIntent(intent)
55 | }
56 | //TODO: Add your own actions here, for example: When a use clicks a notification.
57 | INTENT_ACTION_CLICKED_NOTIFICATION -> {
58 | clickedNotification(intent)
59 | }
60 | }
61 | }
62 |
63 | private fun navigateFromIntent(intent: Intent) {
64 | val uri = intent.data ?: return
65 | val query = uri.query ?: return
66 | if (uri.scheme != "geo") return
67 | CarToast.makeText(carContext, "Navigating to $query", CarToast.LENGTH_LONG).show()
68 | }
69 |
70 | private fun clickedNotification(intent: Intent) {
71 | CarToast.makeText(carContext, "Clicked notification to $intent", CarToast.LENGTH_LONG)
72 | .show()
73 | }
74 |
75 | companion object {
76 | const val LOG_TAG = "MyCarSession"
77 | const val INTENT_ACTION_CLICKED_NOTIFICATION = "clicked_notification"
78 |
79 | //TODO: Add your own navigation logic
80 | var isRouteActive = false
81 | }
82 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/PhonePermissionActivity.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.os.Bundle
6 |
7 | class PhonePermissionActivity : Activity() {
8 |
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 |
12 | requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 123)
13 | }
14 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/extentions/CarBuilders+Extentions.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.extentions
2 |
3 | import androidx.car.app.model.Action
4 | import androidx.car.app.model.ActionStrip
5 | import androidx.car.app.model.CarIcon
6 | import androidx.car.app.model.ItemList
7 | import androidx.car.app.model.MessageTemplate
8 | import androidx.car.app.model.Row
9 | import androidx.car.app.model.SearchTemplate
10 | import androidx.car.app.navigation.model.Maneuver
11 | import androidx.car.app.navigation.model.NavigationTemplate
12 |
13 | /*
14 | * This should reduce the number of .build()-calls everywhere.
15 | * Just return the builder, and the .build() will be called when needed.
16 | */
17 |
18 | fun ItemList.Builder.addItem(itemBuilder: Row.Builder): ItemList.Builder {
19 | this.addItem(itemBuilder.build())
20 | return this
21 | }
22 |
23 | fun ActionStrip.Builder.addAction(actionBuilder: Action.Builder): ActionStrip.Builder {
24 | this.addAction(actionBuilder.build())
25 | return this
26 | }
27 |
28 | fun Action.Builder.setIcon(carIconBuilder: CarIcon.Builder): Action.Builder {
29 | this.setIcon(carIconBuilder.build())
30 | return this
31 | }
32 |
33 | fun SearchTemplate.Builder.setItemList(itemListBuilder: ItemList.Builder): SearchTemplate.Builder {
34 | this.setItemList(itemListBuilder.build())
35 | return this
36 | }
37 |
38 | fun NavigationTemplate.Builder.setActionStrip(actionStripBuilder: ActionStrip.Builder): NavigationTemplate.Builder {
39 | this.setActionStrip(actionStripBuilder.build())
40 | return this
41 | }
42 |
43 | fun NavigationTemplate.Builder.setMapActionStrip(actionStripBuilder: ActionStrip.Builder): NavigationTemplate.Builder {
44 | this.setMapActionStrip(actionStripBuilder.build())
45 | return this
46 | }
47 |
48 | fun MessageTemplate.Builder.setIcon(carIcon: CarIcon.Builder): MessageTemplate.Builder {
49 | this.setIcon(carIcon.build())
50 | return this
51 | }
52 |
53 | fun Row.Builder.setImage(carIcon: CarIcon.Builder): Row.Builder {
54 | this.setImage(carIcon.build())
55 | return this
56 | }
57 |
58 | fun Maneuver.Builder.setIcon(carIcon: CarIcon.Builder): Maneuver.Builder {
59 | this.setIcon(carIcon.build())
60 | return this
61 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/extentions/CarContext+Extentions.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.extentions
2 |
3 | import androidx.car.app.AppManager
4 | import androidx.car.app.CarContext
5 | import androidx.car.app.ScreenManager
6 |
7 | val CarContext.screenManager: ScreenManager
8 | get() = getCarService(CarContext.SCREEN_SERVICE) as ScreenManager
9 |
10 | val CarContext.appManager: AppManager
11 | get() = getCarService(CarContext.APP_SERVICE) as AppManager
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/extentions/Context+Extensions.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.extentions
2 |
3 | import android.content.Context
4 | import android.view.WindowManager
5 |
6 | val Context.windowManager: WindowManager
7 | get() = getSystemService(Context.WINDOW_SERVICE) as WindowManager
8 |
9 |
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/extentions/CoroutinesHelper.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.extentions
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.Job
6 | import kotlinx.coroutines.cancel
7 | import kotlinx.coroutines.launch
8 | import kotlin.coroutines.CoroutineContext
9 |
10 | /**
11 | * Use this to run a block on the main Android thread.
12 | * This should be used only for interacting with the UI and performing quick work.
13 | *
14 | * Examples include calling suspend functions, running Android UI framework operations, and updating LiveData objects.
15 | */
16 | fun runOnMainThread(block: suspend CoroutineScope.() -> Unit) =
17 | launchOnContext(Dispatchers.Main, block)
18 |
19 | /**
20 | * Launch the given block on the given coroutine context
21 | * The scope will be cancelled after completion to prevent leaks
22 | */
23 | fun launchOnContext(
24 | coroutineContext: CoroutineContext,
25 | block: suspend CoroutineScope.() -> Unit
26 | ): Job =
27 | CoroutineScope(coroutineContext).let { scope ->
28 | scope.launch(block = block).apply {
29 | invokeOnCompletion {
30 | scope.cancel()
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/screens/CarMapScreen.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.screens
2 |
3 | import androidx.car.app.CarContext
4 | import androidx.car.app.CarToast
5 | import androidx.car.app.Screen
6 | import androidx.car.app.model.Action
7 | import androidx.car.app.model.ActionStrip
8 | import androidx.car.app.model.CarIcon
9 | import androidx.car.app.model.Template
10 | import androidx.car.app.navigation.model.NavigationTemplate
11 | import androidx.core.graphics.drawable.IconCompat
12 | import nl.flitsmeister.car_common.CarMapRenderer
13 | import nl.flitsmeister.car_common.R
14 | import nl.flitsmeister.car_common.extentions.addAction
15 | import nl.flitsmeister.car_common.extentions.screenManager
16 | import nl.flitsmeister.car_common.extentions.setActionStrip
17 | import nl.flitsmeister.car_common.extentions.setIcon
18 | import nl.flitsmeister.car_common.extentions.setMapActionStrip
19 |
20 | class CarMapScreen(
21 | private val carContext: CarContext,
22 | private val carMapRenderer: CarMapRenderer
23 | ) : Screen(carContext) {
24 |
25 | override fun onGetTemplate(): Template {
26 | val templateBuilder = NavigationTemplate.Builder()
27 | templateBuilder.apply {
28 | setActionStrip(buildActionStrip())
29 | if (carContext.carAppApiLevel >= 2) {
30 | setMapActionStrip(buildMapActionStrip(carMapRenderer))
31 | }
32 | }
33 | return templateBuilder.build()
34 | }
35 |
36 | private fun buildActionStrip(): ActionStrip.Builder {
37 | val actionStripBuilder = ActionStrip.Builder()
38 | actionStripBuilder.apply {
39 | addAction(Action.Builder().apply {
40 | setTitle("Test")
41 | setOnClickListener {
42 | CarToast.makeText(carContext, "Test", CarToast.LENGTH_LONG).show()
43 | }
44 | })
45 | addAction(Action.Builder().apply {
46 | //setTitle("Menu") //There can be only 1 with a title
47 | setIcon(
48 | CarIcon.Builder(
49 | IconCompat.createWithResource(
50 | carContext,
51 | R.drawable.ic_menu
52 | )
53 | )
54 | )
55 | setOnClickListener {
56 | carContext.screenManager.push(CarMenuScreen(carContext))
57 | }
58 | })
59 | return actionStripBuilder
60 | }
61 | }
62 |
63 | private fun buildMapActionStrip(carMapRenderer: CarMapRenderer): ActionStrip.Builder {
64 | val actionStripBuilder = ActionStrip.Builder()
65 | actionStripBuilder.apply {
66 | addAction(Action.PAN) // Needed to enable map interactivity! (pan and zoom gestures)
67 | addAction(Action.Builder().apply {
68 | //setTitle("Zoom in")
69 | setIcon(
70 | CarIcon.Builder(
71 | IconCompat.createWithResource(
72 | carContext,
73 | R.drawable.ic_zoom_in
74 | )
75 | )
76 | )
77 | setOnClickListener {
78 | carMapRenderer.zoomInFromButton()
79 | }
80 | })
81 | addAction(Action.Builder().apply {
82 | //setTitle("Zoom out")
83 | setIcon(
84 | CarIcon.Builder(
85 | IconCompat.createWithResource(
86 | carContext,
87 | R.drawable.ic_zoom_out
88 | )
89 | )
90 | )
91 | setOnClickListener {
92 | carMapRenderer.zoomOutFromButton()
93 | }
94 | })
95 | }
96 | return actionStripBuilder
97 | }
98 |
99 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/screens/CarMenuScreen.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.screens
2 |
3 | import androidx.car.app.CarContext
4 | import androidx.car.app.CarToast
5 | import androidx.car.app.Screen
6 | import androidx.car.app.model.Action
7 | import androidx.car.app.model.ItemList
8 | import androidx.car.app.model.ListTemplate
9 | import androidx.car.app.model.Row
10 | import androidx.car.app.model.SectionedItemList
11 | import androidx.car.app.model.Template
12 | import nl.flitsmeister.car_common.MyCarSession
13 | import nl.flitsmeister.car_common.extentions.addItem
14 | import nl.flitsmeister.car_common.extentions.screenManager
15 |
16 | class CarMenuScreen(carContext: CarContext) :
17 | Screen(carContext) {
18 | override fun onGetTemplate(): Template {
19 | val templateBuilder = ListTemplate.Builder().apply {
20 | setTitle("Menu")
21 | setHeaderAction(Action.BACK)
22 | addSectionedList(buildNavList())
23 | addSectionedList(buildDevList())
24 | }
25 |
26 | return templateBuilder.build()
27 | }
28 |
29 | private fun buildNavList(): SectionedItemList {
30 | //Navigation Button
31 | val navigationRow = Row.Builder().apply {
32 | setTitle(
33 | if (MyCarSession.isRouteActive) {
34 | "Stop Navigation"
35 | } else {
36 | "Start Navigation"
37 | }
38 | )
39 | setOnClickListener {
40 | CarToast.makeText(
41 | carContext,
42 | if (MyCarSession.isRouteActive) {
43 | "Stopping Navigation"
44 | } else {
45 | "Starting Navigation"
46 | },
47 | CarToast.LENGTH_LONG
48 | ).show()
49 | MyCarSession.isRouteActive = !MyCarSession.isRouteActive
50 | carContext.screenManager.pop()
51 | }
52 |
53 | }
54 | val sectionedItemList = SectionedItemList.create(
55 | ItemList.Builder().apply {
56 | addItem(navigationRow)
57 | }.build(),
58 | "Navigation"
59 | )
60 | return sectionedItemList
61 | }
62 |
63 | private fun buildDevList(): SectionedItemList {
64 | //Test CarToast
65 | val testCarToast = Row.Builder().apply {
66 | setTitle("Test CarToast")
67 | setOnClickListener {
68 | CarToast.makeText(
69 | carContext,
70 | "Test CarToast",
71 | CarToast.LENGTH_LONG
72 | ).show()
73 | carContext.screenManager.pop()
74 | }
75 | }
76 |
77 | val sectionedItemList = SectionedItemList.create(
78 | ItemList.Builder().apply {
79 | addItem(testCarToast)
80 | }.build(),
81 | "Developer tools"
82 | )
83 |
84 | return sectionedItemList
85 | }
86 | }
--------------------------------------------------------------------------------
/car_common/src/main/java/nl/flitsmeister/car_common/screens/CarPermissionScreen.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common.screens
2 |
3 | import android.Manifest
4 | import android.content.Intent
5 | import android.content.pm.PackageManager
6 | import android.util.Log
7 | import androidx.car.app.CarContext
8 | import androidx.car.app.CarToast
9 | import androidx.car.app.Screen
10 | import androidx.car.app.ScreenManager
11 | import androidx.car.app.model.Action
12 | import androidx.car.app.model.CarColor
13 | import androidx.car.app.model.CarIcon
14 | import androidx.car.app.model.MessageTemplate
15 | import androidx.car.app.model.ParkedOnlyOnClickListener
16 | import androidx.car.app.model.Template
17 | import androidx.core.content.ContextCompat
18 | import androidx.core.graphics.drawable.IconCompat
19 | import nl.flitsmeister.car_common.MyCarAppService
20 | import nl.flitsmeister.car_common.PhonePermissionActivity
21 | import nl.flitsmeister.car_common.R
22 | import java.util.concurrent.Executor
23 |
24 | class CarPermissionScreen(carContext: CarContext) : Screen(carContext) {
25 | override fun onGetTemplate(): Template {
26 | Log.v(LOG_TAG, "CarPermissionScreen.onGetTemplate")
27 | val message = if (MyCarAppService.appPlatform == "AAOS") {
28 | carContext.getString(R.string.aaos_no_location_permission_desc)
29 | } else {
30 | carContext.getString(R.string.aa_no_location_permission_desc)
31 | }
32 | val templateBuilder = MessageTemplate.Builder(message).apply {
33 | setTitle(carContext.getString(R.string.app_name))
34 | setIcon(
35 | CarIcon.Builder(
36 | IconCompat.createWithResource(
37 | carContext,
38 | R.drawable.ic_launcher_foreground
39 | )
40 | ).build()
41 | )
42 | if (MyCarAppService.appPlatform == "AAOS") {
43 | addAction(
44 | Action.Builder()
45 | .setBackgroundColor(CarColor.BLUE)
46 | .setTitle(carContext.getString(R.string.aaos_open_location_permission))
47 | .setOnClickListener(
48 | ParkedOnlyOnClickListener.create(::clickedFixPermissionAAOS)
49 | )
50 | .build()
51 | )
52 | } else {
53 | addAction(
54 | Action.Builder()
55 | .setBackgroundColor(CarColor.BLUE)
56 | .setTitle(carContext.getString(R.string.aa_open_location_permission))
57 | .setOnClickListener(
58 | ParkedOnlyOnClickListener.create(::clickedFixPermissionAA)
59 | )
60 | .build()
61 | )
62 | }
63 | addAction(
64 | Action.Builder()
65 | .setBackgroundColor(CarColor.DEFAULT)
66 | .setTitle(carContext.getString(R.string.close))
67 | .setOnClickListener {
68 | carContext.finishCarApp()
69 | }
70 | .build()
71 | )
72 | }
73 |
74 | return templateBuilder.build()
75 | }
76 |
77 | private fun clickedFixPermissionAAOS() {
78 | //Ask for permissions
79 | val requiredPermissions = listOf(
80 | Manifest.permission.ACCESS_FINE_LOCATION
81 | )
82 | val myExecutor = Executor { command -> command?.run() }
83 | carContext.requestPermissions(
84 | requiredPermissions,
85 | myExecutor, //An Executor makes sure code is run on the correct thread.
86 | ) { approved, rejected ->
87 | Log.v("FM AAOS", "$approved $rejected")
88 | if (approved.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
89 | //Granted
90 | CarToast.makeText(
91 | carContext,
92 | carContext.getString(R.string.permission_granted),
93 | CarToast.LENGTH_LONG
94 | )
95 | .show()
96 | //Close this screen
97 | val screenManager =
98 | carContext.getCarService(CarContext.SCREEN_SERVICE) as ScreenManager
99 | screenManager.popTo("ROOT")
100 | }
101 | }
102 | }
103 |
104 | private fun clickedFixPermissionAA() {
105 | //If this function is called, you're standing still, Android Auto has already checked that.
106 | //Check if permission is already given
107 | if (ContextCompat.checkSelfPermission(
108 | carContext,
109 | Manifest.permission.ACCESS_FINE_LOCATION
110 | ) != PackageManager.PERMISSION_GRANTED
111 | ) {
112 | //Not granted
113 | //Open screen on phone
114 | val intent = Intent(carContext, PhonePermissionActivity::class.java)
115 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
116 | try {
117 | carContext.startActivity(intent)
118 | CarToast.makeText(
119 | carContext,
120 | carContext.getString(R.string.aa_opened_on_phone),
121 | CarToast.LENGTH_LONG
122 | ).show()
123 | } catch (e: Exception) {
124 | e.printStackTrace()
125 | CarToast.makeText(
126 | carContext,
127 | carContext.getString(R.string.aa_open_on_phone_manual),
128 | CarToast.LENGTH_LONG
129 | ).show()
130 | }
131 | } else {
132 | //Granted
133 | CarToast.makeText(
134 | carContext,
135 | carContext.getString(R.string.permission_granted),
136 | CarToast.LENGTH_LONG
137 | ).show()
138 | //Close this screen
139 | val screenManager =
140 | carContext.getCarService(CarContext.SCREEN_SERVICE) as ScreenManager
141 | screenManager.popTo("ROOT")
142 | }
143 | }
144 |
145 | companion object {
146 | const val LOG_TAG = "CarPermissionScreen"
147 | }
148 | }
--------------------------------------------------------------------------------
/car_common/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/car_common/src/main/res/drawable/ic_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/car_common/src/main/res/drawable/ic_zoom_in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/drawable/ic_zoom_in.png
--------------------------------------------------------------------------------
/car_common/src/main/res/drawable/ic_zoom_out.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/drawable/ic_zoom_out.png
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/car_common/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/car_common/src/main/res/values/car.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - fdb00c43dbde8b51cb312aa81d3b5fa17713adb94b28f598d77f8eb89daceedf,
5 | com.google.android.projection.gearhead
6 |
7 | - 70811a3eacfd2e83e18da9bfede52df16ce91f2e69a44d21f18ab66991130771,
8 | com.google.android.projection.gearhead
9 |
10 | - 1975b2f17177bc89a5dff31f9e64a6cae281a53dc1d1d59b1d147fe1c82afa00,
11 | com.google.android.projection.gearhead
12 |
13 |
14 | - c241ffbc8e287c4e9a4ad19632ba1b1351ad361d5177b7d7b29859bd2b7fc631,
15 | com.google.android.apps.automotive.templates.host
16 |
17 | - dd66deaf312d8daec7adbe85a218ecc8c64f3b152f9b5998d5b29300c2623f61,
18 | com.google.android.apps.automotive.templates.host
19 |
20 | - 50e603d333c6049a37bd751375d08f3bd0abebd33facd30bd17b64b89658b421,
21 | com.google.android.apps.automotive.templates.host
22 |
23 |
24 |
33 |
--------------------------------------------------------------------------------
/car_common/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #996A747B
4 |
--------------------------------------------------------------------------------
/car_common/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #111725
4 |
--------------------------------------------------------------------------------
/car_common/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | MapLibreCar
4 | © OpenStreetMap
5 | No permission, please enable in settings
6 | No permission, please enable on phone
7 | Open location permission
8 | Open on phone
9 | Close
10 | Opened on phone
11 | Check your phone
12 | Permission granted
13 |
--------------------------------------------------------------------------------
/car_common/src/main/res/xml/automotive_app_desc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/car_common/src/test/java/nl/flitsmeister/car_common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package nl.flitsmeister.car_common
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.7.3"
3 | kotlin = "2.1.0"
4 | coreKtx = "1.15.0"
5 | junit = "4.13.2"
6 | junitVersion = "1.2.1"
7 | espressoCore = "3.6.1"
8 | lifecycleRuntimeKtx = "2.8.7"
9 | activityCompose = "1.9.3"
10 | composeBom = "2024.12.01"
11 | appcompat = "1.7.0"
12 | material = "1.12.0"
13 | carApp = "1.4.0"
14 | mapLibreSdk = "11.7.0"
15 | mapLibreNav = "3.0.0"
16 |
17 | [libraries]
18 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
19 | junit = { group = "junit", name = "junit", version.ref = "junit" }
20 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
21 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
22 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
23 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
24 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
25 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
26 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
27 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
28 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
29 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
30 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
31 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
32 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
33 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
34 | car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }
35 | car-app-automotive = { group = "androidx.car.app", name = "app-automotive", version.ref = "carApp" }
36 | maplibre-sdk = { group = "org.maplibre.gl", name = "android-sdk", version.ref = "mapLibreSdk" }
37 | maplibre-nav = { group = "com.github.maplibre", name = "maplibre-navigation-android", version.ref = "mapLibreNav" }
38 |
39 | [plugins]
40 | android-application = { id = "com.android.application", version.ref = "agp" }
41 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
42 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
43 | android-library = { id = "com.android.library", version.ref = "agp" }
44 |
45 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Nov 07 14:33:30 CET 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maplibre/MapLibre-Android-Auto-Sample/8d080c46fb3d97c062264f779d2a31619c99f7bd/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | maven { url = uri("https://jitpack.io") }
20 | }
21 | }
22 |
23 | rootProject.name = "MapLibreCar"
24 | include(":app")
25 | include(":automotive")
26 | include(":common")
27 | include(":car_common")
28 |
--------------------------------------------------------------------------------