From cdb28ce7dc14326702de6b202fd680490fa2b9d2 Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:40:22 -0700 Subject: [PATCH 1/6] feat: update to maps 20.0.0 and add usage attribution --- gradle/libs.versions.toml | 20 +++++----- library/build.gradle.kts | 40 ++++++++++++++++++- library/src/main/AndroidManifest.xml | 15 ++++++- .../attribution/AttributionIdInitializer.kt | 38 ++++++++++++++++++ 4 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 library/src/main/java/com/google/maps/android/utils/attribution/AttributionIdInitializer.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80478e9d3..c26d90492 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,29 +1,30 @@ [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" [libraries] appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -34,6 +35,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" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index c7d9b1979..2dfa18aa2 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,6 +68,7 @@ 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) @@ -89,3 +91,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..d7bf81858 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/utils/attribution/AttributionIdInitializer.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 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 + +/** + * Initializes the AttributionId at application startup. + */ +internal class AttributionIdInitializer : Initializer { + override fun create(context: Context) { + MapsApiSettings.addInternalUsageAttributionId( + /* context = */ context, + /* internalUsageAttributionId = */ AttributionId.VALUE + ) + } + + override fun dependencies(): List>> { + return emptyList() + } +} From 876a8f92db21c6329476325e50fed8f85e24aa5f Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:02:50 -0700 Subject: [PATCH 2/6] docs: add internal usage attribution disable instructions and secrets setup --- README.md | 34 +++++++++++++++++-- .../attribution/AttributionIdInitializer.kt | 4 ++- 2 files changed, 34 insertions(+), 4 deletions(-) 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/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 index d7bf81858..415bb7738 100644 --- 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 @@ -22,10 +22,12 @@ import com.google.android.gms.maps.MapsApiSettings import com.google.maps.android.utils.meta.AttributionId /** - * Initializes the AttributionId at application startup. + * 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 From e91cc59ed531385c514b2eb186c9e519fdefa95b Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:17:00 -0700 Subject: [PATCH 3/6] style: fix long kdoc lines in AttributionIdInitializer --- .../maps/android/utils/attribution/AttributionIdInitializer.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 415bb7738..0c3600482 100644 --- 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 @@ -22,7 +22,8 @@ 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. + * 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 { From 0d2bff059547a190724008a5d42c97f67b7462f5 Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:33:03 -0700 Subject: [PATCH 4/6] test: add unit test for AttributionIdInitializer --- gradle/libs.versions.toml | 2 + library/build.gradle.kts | 1 + .../AttributionIdInitializerTest.kt | 63 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c26d90492..c8f858c86 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ lint = "32.0.0" org-jacoco-core = "0.8.14" material = "1.13.0" gradleMavenPublishPlugin = "0.36.0" +androidx-test-core = "1.6.1" [libraries] appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -57,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 2dfa18aa2..34b80a424 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -75,6 +75,7 @@ dependencies { testImplementation(libs.kxml2) testImplementation(libs.mockk) testImplementation(libs.kotlin.test) + testImplementation(libs.androidx.test.core) testImplementation(libs.truth) implementation(libs.kotlin.stdlib.jdk8) 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..23a7f1272 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2025 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 + ) + } + } +} From 1c05fbce8c3c4530f6867508e6fb66961da16987 Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:35:15 -0700 Subject: [PATCH 5/6] docs: update copyright years to 2026 --- .../maps/android/utils/attribution/AttributionIdInitializer.kt | 2 +- .../android/utils/attribution/AttributionIdInitializerTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 0c3600482..4dbbb675d 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google LLC + * 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. 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 index 23a7f1272..28410e054 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google LLC + * 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. From 50081cd2f9bc3fd362edc2c0f6a70aa08f50ae3c Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:49:22 -0700 Subject: [PATCH 6/6] build: update androidx.test:core to 1.7.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8f858c86..4c04614a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ lint = "32.0.0" org-jacoco-core = "0.8.14" material = "1.13.0" gradleMavenPublishPlugin = "0.36.0" -androidx-test-core = "1.6.1" +androidx-test-core = "1.7.0" [libraries] appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }