diff --git a/.releaserc b/.releaserc index 837a4bf4..8804b8ec 100644 --- a/.releaserc +++ b/.releaserc @@ -6,14 +6,12 @@ plugins: - - "@google/semantic-release-replace-plugin" - replacements: - files: - - "./Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts" - - "./Maps3DSamples/advanced/app/build.gradle.kts" - from: "versionName = \".*\"" - to: "versionName = \"${nextRelease.version}\"" + - "./build.gradle.kts" + from: "version = \".*\"" + to: "version = \"${nextRelease.version}\"" - - "@semantic-release/git" - assets: - - "Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts" - - "Maps3DSamples/advanced/app/build.gradle.kts" + - "build.gradle.kts" - - "@semantic-release/github" - assets: - "./advanced-kotlin-debug.apk" diff --git a/Maps3DSamples/ApiDemos/java-app/build.gradle.kts b/Maps3DSamples/ApiDemos/java-app/build.gradle.kts index 8eedc77d..33483e41 100644 --- a/Maps3DSamples/ApiDemos/java-app/build.gradle.kts +++ b/Maps3DSamples/ApiDemos/java-app/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.play.services.base) // "com.google.android.gms:play-services-base:18.10.0" + implementation(project(":library")) implementation(project(":Maps3DSamples:ApiDemos:common")) testImplementation(libs.junit) // "junit:junit:4.13.2" diff --git a/Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts b/Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts index 9ba8d840..543e7bcb 100644 --- a/Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts +++ b/Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts @@ -35,7 +35,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 - versionName = "1.13.0" + versionName = project.version.toString() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -104,6 +104,7 @@ dependencies { debugImplementation(libs.androidx.ui.test.manifest) implementation(libs.play.services.base) // "com.google.android.gms:play-services-base:18.10.0" + implementation(project(":library")) implementation(project(":Maps3DSamples:ApiDemos:common")) } diff --git a/Maps3DSamples/ComposeDemos/app/build.gradle.kts b/Maps3DSamples/ComposeDemos/app/build.gradle.kts index ea7d984b..affdad70 100644 --- a/Maps3DSamples/ComposeDemos/app/build.gradle.kts +++ b/Maps3DSamples/ComposeDemos/app/build.gradle.kts @@ -88,6 +88,7 @@ android { dependencies { coreLibraryDesugaring(libs.desugar.jdk.libs) + implementation(project(":library")) implementation(project(":maps3d-compose")) implementation(project(":Maps3DSamples:ApiDemos:common")) diff --git a/Maps3DSamples/advanced/app/build.gradle.kts b/Maps3DSamples/advanced/app/build.gradle.kts index 4462740b..ebad08d8 100644 --- a/Maps3DSamples/advanced/app/build.gradle.kts +++ b/Maps3DSamples/advanced/app/build.gradle.kts @@ -39,7 +39,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 - versionName = "1.13.0" + versionName = project.version.toString() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -75,7 +75,7 @@ android { } dependencies { - + implementation(project(":library")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) diff --git a/PlacesUIKit3D/build.gradle.kts b/PlacesUIKit3D/build.gradle.kts index f58fdf31..395a721c 100644 --- a/PlacesUIKit3D/build.gradle.kts +++ b/PlacesUIKit3D/build.gradle.kts @@ -129,6 +129,8 @@ android { // The `dependencies` block is where we declare all the external libraries the app needs. // These are fetched from repositories like Maven Central and Google's Maven repository. dependencies { + implementation(project(":library")) + // --- Core AndroidX & UI Libraries --- // These are foundational libraries for building modern Android apps. implementation(libs.androidx.core.ktx) diff --git a/README.md b/README.md index d2a6ee0d..4c7c4048 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,24 @@ under [releases](https://github.com/googlemaps/android-maps3d-samples/releases) If you are upgrading from an older version of the SDK, please refer to the [Migration Guide](MIGRATION_GUIDE.md) for details on breaking changes and how to update your codebase. +## 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/build.gradle.kts b/build.gradle.kts index 52d07da2..1f7a1b22 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,12 @@ plugins { alias(libs.plugins.spotless) apply false } +allprojects { + // {x-release-please-start-version} + version = "1.13.0" + // {x-release-please-end} +} + subprojects { tasks.withType().configureEach { jvmArgs("--add-opens=java.base/jdk.internal.access=ALL-UNNAMED") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ac6ed5f7..1071abee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,9 @@ mapsUtils = "6.0.0" androidx-core-ktx = "1.8.9" androidxArchCoreTesting = "2.2.0" kotlinxCoroutines = "1.8.0" +mockk = "1.14.11" +startup-runtime = "1.2.0" +playServicesMaps = "20.0.0" [libraries] androidx-arch-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "androidxArchCoreTesting" } @@ -86,6 +89,9 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx android-maps-utils = { module = "com.google.maps.android:android-maps-utils", version.ref = "mapsUtils" } android-maps-utils-core = { module = "com.google.maps.android:android-maps-utils-core", version.ref = "mapsUtils" } androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startup-runtime" } +play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/library/.gitignore b/library/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/library/.gitignore @@ -0,0 +1 @@ +/build diff --git a/library/build.gradle.kts b/library/build.gradle.kts new file mode 100644 index 00000000..fe4bc6ed --- /dev/null +++ b/library/build.gradle.kts @@ -0,0 +1,114 @@ +/* + * 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. + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.spotless) +} + +configure { + kotlin { + target("**/*.kt") + ktlint().editorConfigOverride( + mapOf( + "indent_size" to "4", + "ktlint_function_naming_ignore_when_annotated_with" to "Composable", + "ktlint_standard_max-line-length" to "disabled", + ), + ) + trimTrailingWhitespace() + endWithNewline() + } +} + +android { + namespace = "com.example.maps3dsamples.library" + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } +} + +dependencies { + implementation(libs.play.services.maps) + implementation(libs.startup.runtime) + testImplementation(libs.junit) + testImplementation(libs.robolectric) + testImplementation(libs.mockk) + testImplementation(libs.androidx.core) +} + +abstract class GenerateArtifactIdTask : DefaultTask() { + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @get:Input + abstract val version: Property + + @TaskAction + fun generate() { + val dir = outputDir.get().asFile + val packageName = "com.example.maps3dsamples.library.utils.meta" + val packagePath = packageName.replace('.', '/') + val outputFile = File(dir, "$packagePath/ArtifactId.kt") + outputFile.parentFile.mkdirs() + val attributionId = "gmp_git_androidmaps3dsamples_v${version.get()}" + 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(), + ) + } +} + +val generateArtifactIdFile = tasks.register("generateArtifactIdFile") { + outputDir.set(layout.buildDirectory.dir("generated/source/artifactId")) + version.set(project.version.toString()) +} + +androidComponents { + onVariants { variant -> + variant.sources.java?.addGeneratedSourceDirectory( + generateArtifactIdFile, + GenerateArtifactIdTask::outputDir, + ) + } +} diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro new file mode 100644 index 00000000..3add6774 --- /dev/null +++ b/library/consumer-rules.pro @@ -0,0 +1 @@ +# Consumer ProGuard rules for attribution ID initializer diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 00000000..150f843f --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/library/src/main/java/com/example/maps3dsamples/library/utils/attribution/AttributionIdInitializer.kt b/library/src/main/java/com/example/maps3dsamples/library/utils/attribution/AttributionIdInitializer.kt new file mode 100644 index 00000000..b31cdfe1 --- /dev/null +++ b/library/src/main/java/com/example/maps3dsamples/library/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.example.maps3dsamples.library.utils.attribution + +import android.content.Context +import androidx.annotation.Keep +import androidx.startup.Initializer +import com.example.maps3dsamples.library.utils.meta.AttributionId +import com.google.android.gms.maps.MapsApiSettings + +/** + * 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. + */ +@Keep +internal class AttributionIdInitializer : Initializer { + override fun create(context: Context) { + MapsApiSettings.addInternalUsageAttributionId( + // context = + context, + // internalUsageAttributionId = + AttributionId.VALUE, + ) + } + + override fun dependencies(): List>> = emptyList() +} diff --git a/library/src/test/java/com/example/maps3dsamples/library/utils/attribution/AttributionIdInitializerTest.kt b/library/src/test/java/com/example/maps3dsamples/library/utils/attribution/AttributionIdInitializerTest.kt new file mode 100644 index 00000000..0c4f75b3 --- /dev/null +++ b/library/src/test/java/com/example/maps3dsamples/library/utils/attribution/AttributionIdInitializerTest.kt @@ -0,0 +1,61 @@ +/* + * 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.example.maps3dsamples.library.utils.attribution + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.example.maps3dsamples.library.utils.meta.AttributionId +import com.google.android.gms.maps.MapsApiSettings +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, + ) + } + } +} diff --git a/maps3d-compose-demo/build.gradle.kts b/maps3d-compose-demo/build.gradle.kts index f450ae7d..11bd32d7 100644 --- a/maps3d-compose-demo/build.gradle.kts +++ b/maps3d-compose-demo/build.gradle.kts @@ -84,6 +84,7 @@ android { } dependencies { + implementation(project(":library")) implementation(project(":maps3d-compose")) implementation(libs.androidx.core.ktx) diff --git a/settings.gradle.kts b/settings.gradle.kts index 494cd19c..a03595f8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -69,3 +69,6 @@ include(":maps3d-compose-demo") // Visual Testing include(":visual-testing") + +// Attribution Library +include(":library") diff --git a/snippets/java-app/build.gradle.kts b/snippets/java-app/build.gradle.kts index eecb7bb0..7c620f0f 100644 --- a/snippets/java-app/build.gradle.kts +++ b/snippets/java-app/build.gradle.kts @@ -69,6 +69,7 @@ android { dependencies { coreLibraryDesugaring(libs.desugar.jdk.libs) + implementation(project(":library")) implementation(project(":snippets:common")) implementation(libs.androidx.core.ktx) diff --git a/snippets/kotlin-app/build.gradle.kts b/snippets/kotlin-app/build.gradle.kts index cf1fd925..fc19f1f2 100644 --- a/snippets/kotlin-app/build.gradle.kts +++ b/snippets/kotlin-app/build.gradle.kts @@ -71,6 +71,7 @@ android { dependencies { coreLibraryDesugaring(libs.desugar.jdk.libs) + implementation(project(":library")) implementation(project(":snippets:common")) implementation(libs.androidx.core.ktx)