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 + ) + } + } +}