diff --git a/README.md b/README.md
index 1c1693f59..cbed94e74 100644
--- a/README.md
+++ b/README.md
@@ -61,8 +61,18 @@ This repository includes a [sample app](demo) that illustrates the use of this l
To run the demo app, ensure you've met the requirements above then:
1. Clone the repository
-1. Add a file `local.properties` in the root project (this file should *NOT* be under version control to protect your API key)
-1. Add a single line to `local.properties` that looks like `MAPS_API_KEY=YOUR_API_KEY`, where `YOUR_API_KEY` is the API key you obtained earlier
+1. Add a file `secrets.properties` in the root project (this file should *NOT* be under version control to protect your API key)
+1. Add the following keys to `secrets.properties`:
+ - `MAPS_API_KEY`: **Required**. Your Google Maps API Key. Ensure it has the **Maps SDK for Android** enabled.
+ - `PLACES_API_KEY`: **Optional**. Required for demos using the Places API (e.g., Heatmaps with Places). Ensure the **Places API** is enabled.
+ - `MAP_ID`: **Optional**. Required for demos using Advanced Markers or Cloud-based Map Styling.
+
+ For example:
+ ```properties
+ MAPS_API_KEY=YOUR_MAPS_API_KEY
+ PLACES_API_KEY=YOUR_PLACES_API_KEY
+ MAP_ID=YOUR_MAP_ID
+ ```
1. Build and run the `debug` variant for the Maps SDK for Android version
### Setting up the Map ID
@@ -70,7 +80,7 @@ To run the demo app, ensure you've met the requirements above then:
Some of the features in the demo app, such as Advanced Markers, require a Map ID. You can learn more about map IDs in the [official documentation](https://developers.google.com/maps/documentation/android-sdk/map-ids/mapid-over). You can set the Map ID in one of the following ways:
1. **`secrets.properties`:** Add a line to your `secrets.properties` file with your Map ID:
- ```
+ ```properties
MAP_ID=YOUR_MAP_ID
```
@@ -144,6 +154,24 @@ By default, the `Source` is set to `Source.DEFAULT`, but you can also specify `S
+## Internal usage attribution ID
+
+This library calls the `addInternalUsageAttributionId` method, which helps Google understand which libraries and samples are helpful to developers and is optional. Instructions for opting out of the identifier are provided below.
+
+If you wish to disable this, you can do so by removing the initializer in your `AndroidManifest.xml` using the `tools:node="remove"` attribute:
+
+```xml
+
+
+
+```
+
## Contributing
Contributions are welcome and encouraged! If you'd like to contribute, send us a [pull request] and refer to our [code of conduct] and [contributing guide].
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 80478e9d3..4c04614a1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,29 +1,31 @@
[versions]
compileSdk = "36"
targetSdk = "36"
-minimumSdk = "21"
+minimumSdk = "23"
appcompat = "1.7.1"
dokka-gradle-plugin = "2.1.0"
-gradle = "8.13.1"
+gradle = "8.13.2"
jacoco-android = "0.2.1"
lifecycle-extensions = "2.2.0"
-lifecycle-viewmodel-ktx = "2.9.4"
+lifecycle-viewmodel-ktx = "2.10.0"
+startup-runtime = "1.2.0"
kotlin = "2.2.21"
kotlinx-coroutines = "1.10.2"
junit = "4.13.2"
-mockito-core = "5.20.0"
+mockito-core = "5.21.0"
secrets-gradle-plugin = "2.0.1"
truth = "1.4.5"
-play-services-maps = "19.2.0"
+play-services-maps = "20.0.0"
core-ktx = "1.17.0"
-robolectric = "4.16"
+robolectric = "4.16.1"
kxml2 = "2.3.0"
-mockk = "1.14.6"
-lint = "31.13.1"
+mockk = "1.14.7"
+lint = "32.0.0"
org-jacoco-core = "0.8.14"
material = "1.13.0"
-gradleMavenPublishPlugin = "0.35.0"
+gradleMavenPublishPlugin = "0.36.0"
+androidx-test-core = "1.7.0"
[libraries]
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
@@ -34,6 +36,7 @@ kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", v
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycle-extensions" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle-viewmodel-ktx" }
+startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startup-runtime" }
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
@@ -55,3 +58,4 @@ testutils = { module = "com.android.tools:testutils", version.ref = "lint" }
org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
gradle-maven-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradleMavenPublishPlugin" }
+androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" }
diff --git a/library/build.gradle.kts b/library/build.gradle.kts
index c7d9b1979..34b80a424 100644
--- a/library/build.gradle.kts
+++ b/library/build.gradle.kts
@@ -27,7 +27,7 @@ android {
}
defaultConfig {
compileSdk = libs.versions.compileSdk.get().toInt()
- minSdk = 21
+ minSdk = libs.versions.minimumSdk.get().toInt()
testOptions.targetSdk = libs.versions.targetSdk.get().toInt()
consumerProguardFiles("consumer-rules.pro")
}
@@ -60,6 +60,7 @@ android {
unitTests.isReturnDefaultValues = true
}
namespace = "com.google.maps.android"
+ sourceSets["main"].java.srcDir("build/generated/source/artifactId")
}
dependencies {
@@ -67,12 +68,14 @@ dependencies {
implementation(libs.kotlinx.coroutines.android)
implementation(libs.appcompat)
implementation(libs.core.ktx)
+ implementation(libs.startup.runtime)
lintPublish(project(":lint-checks"))
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.kxml2)
testImplementation(libs.mockk)
testImplementation(libs.kotlin.test)
+ testImplementation(libs.androidx.test.core)
testImplementation(libs.truth)
implementation(libs.kotlin.stdlib.jdk8)
@@ -89,3 +92,39 @@ tasks.register("instrumentTest") {
if (System.getenv("JITPACK") != null) {
apply(plugin = "maven")
}
+
+// START: Attribution ID Generation Logic
+val attributionId = "gmp_git_androidmapsutils_v$version"
+
+val generateArtifactIdFile = tasks.register("generateArtifactIdFile") {
+ description = "Generates an AttributionId object from the project version."
+ group = "build"
+
+ val outputDir = layout.buildDirectory.dir("generated/source/artifactId")
+ val packageName = "com.google.maps.android.utils.meta"
+ val packagePath = packageName.replace('.', '/')
+ val outputFile = outputDir.get().file("$packagePath/ArtifactId.kt").asFile
+
+ outputs.file(outputFile)
+
+ doLast {
+ outputFile.parentFile.mkdirs()
+ outputFile.writeText(
+ """
+ package $packageName
+
+ /**
+ * Automatically generated object containing the library's attribution ID.
+ * This is used to track library usage for analytics.
+ */
+ public object AttributionId {
+ public const val VALUE: String = "$attributionId"
+ }
+ """.trimIndent()
+ )
+ }
+}
+
+tasks.named("preBuild") {
+ dependsOn(generateArtifactIdFile)
+}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
index bb23d7b93..1acde0b75 100644
--- a/library/src/main/AndroidManifest.xml
+++ b/library/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
+
+
+
+
diff --git a/library/src/main/java/com/google/maps/android/utils/attribution/AttributionIdInitializer.kt b/library/src/main/java/com/google/maps/android/utils/attribution/AttributionIdInitializer.kt
new file mode 100644
index 000000000..4dbbb675d
--- /dev/null
+++ b/library/src/main/java/com/google/maps/android/utils/attribution/AttributionIdInitializer.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.maps.android.utils.attribution
+
+import android.content.Context
+import androidx.startup.Initializer
+import com.google.android.gms.maps.MapsApiSettings
+import com.google.maps.android.utils.meta.AttributionId
+
+/**
+ * Adds a usage attribution ID to the initializer, which helps Google understand which libraries
+ * and samples are helpful to developers, such as usage of this library.
+ * To opt out of sending the usage attribution ID, please remove this initializer from your manifest.
+ */
+internal class AttributionIdInitializer : Initializer {
+ override fun create(context: Context) {
+ // See [AttributionIdInitializer]
+ MapsApiSettings.addInternalUsageAttributionId(
+ /* context = */ context,
+ /* internalUsageAttributionId = */ AttributionId.VALUE
+ )
+ }
+
+ override fun dependencies(): List>> {
+ return emptyList()
+ }
+}
diff --git a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt
new file mode 100644
index 000000000..28410e054
--- /dev/null
+++ b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.maps.android.utils.attribution
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import com.google.android.gms.maps.MapsApiSettings
+import com.google.maps.android.utils.meta.AttributionId
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockkStatic
+import io.mockk.runs
+import io.mockk.unmockkStatic
+import io.mockk.verify
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class AttributionIdInitializerTest {
+
+ @Before
+ fun setUp() {
+ mockkStatic(MapsApiSettings::class)
+ every { MapsApiSettings.addInternalUsageAttributionId(any(), any()) } just runs
+ }
+
+ @After
+ fun tearDown() {
+ unmockkStatic(MapsApiSettings::class)
+ }
+
+ @Test
+ fun `create adds internal usage attribution id`() {
+ val context = ApplicationProvider.getApplicationContext()
+ val initializer = AttributionIdInitializer()
+
+ initializer.create(context)
+
+ verify {
+ MapsApiSettings.addInternalUsageAttributionId(
+ context,
+ AttributionId.VALUE
+ )
+ }
+ }
+}