diff --git a/ApiDemos/project/common-ui/build.gradle.kts b/ApiDemos/project/common-ui/build.gradle.kts index dfcd8352b..39a171450 100644 --- a/ApiDemos/project/common-ui/build.gradle.kts +++ b/ApiDemos/project/common-ui/build.gradle.kts @@ -65,7 +65,7 @@ android { } dependencies { - + implementation(project(":library")) implementation(libs.core.ktx) implementation(libs.appcompat) implementation(libs.material) diff --git a/FireMarkers/app/build.gradle.kts b/FireMarkers/app/build.gradle.kts index eb56c8aff..79182eb9c 100644 --- a/FireMarkers/app/build.gradle.kts +++ b/FireMarkers/app/build.gradle.kts @@ -95,6 +95,7 @@ android { } dependencies { + implementation(project(":library")) // --------------------------------------------------------------------------------------------- // AndroidX & Jetpack // diff --git a/README.md b/README.md index f8c3a5217..929a0df53 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,24 @@ To run instrumentation tests on a connected device or emulator, use: Alternatively, use the `gradlew build` command to build the project directly or download an APK under [releases](https://github.com/googlemaps/android-samples/releases). +## 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/WearOS/Wearable/build.gradle.kts b/WearOS/Wearable/build.gradle.kts index d7670782a..b62f505b4 100644 --- a/WearOS/Wearable/build.gradle.kts +++ b/WearOS/Wearable/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { implementation(libs.core.ktx) implementation(platform(libs.kotlin.bom)) implementation(libs.kotlin.stdlib) + implementation(project(":library")) // [END_EXCLUDE] // Modern Android projects use version catalogs to manage dependencies. To include the necessary dependencies, // first add the following to your libs.versions.toml file: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c0f0ae9dd..dcd2deaf2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,6 +58,7 @@ robolectric = "4.16.1" truth = "1.4.5" turbine = "1.2.1" uiautomator = "2.3.0" +mockk = "1.14.11" # Firebase firebaseBom = "34.8.0" @@ -67,12 +68,16 @@ firebaseDatabase = "22.0.1" easypermissions = "3.0.0" volley = "1.2.1" +#Tracker lib +startup-runtime = "1.2.0" + [libraries] # Kotlin kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } +startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startup-runtime" } # AndroidX activity = { module = "androidx.activity:activity", version.ref = "activity" } @@ -130,6 +135,7 @@ turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" } uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } # Firebase firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } diff --git a/library/.gitignore b/library/.gitignore new file mode 100644 index 000000000..796b96d1c --- /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 000000000..06626fd56 --- /dev/null +++ b/library/build.gradle.kts @@ -0,0 +1,117 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +/* + * 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. + */ + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.example.library" + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + lint { + abortOnError = false + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { + disable += setOf("MissingInflatedId", "OnClick") + sarifOutput = layout.buildDirectory.file("reports/lint-results-debug.sarif").get().asFile + } + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + javaParameters.set(true) + } + } +} + +dependencies { + implementation(libs.appcompat) + implementation(libs.core.ktx) + implementation(libs.material) + implementation(libs.startup.runtime) + implementation(libs.play.services.maps) + testImplementation(libs.junit) + testImplementation(libs.robolectric) + testImplementation(libs.mockk) + testImplementation(libs.espresso.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.library.utils.meta" + val packagePath = packageName.replace('.', '/') + val outputFile = File(dir, "$packagePath/ArtifactId.kt") + outputFile.parentFile.mkdirs() + val attributionId = "gmp_git_androidsamples_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(libs.versions.versionName.get()) +} + +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 000000000..e69de29bb diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/library/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a0aa501d8 --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/java/com/example/library/utils/attribution/AttributionIdInitializer.kt b/library/src/main/java/com/example/library/utils/attribution/AttributionIdInitializer.kt new file mode 100644 index 000000000..cc426e512 --- /dev/null +++ b/library/src/main/java/com/example/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.library.utils.attribution + +import android.content.Context +import androidx.annotation.Keep +import androidx.startup.Initializer +import com.example.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/library/utils/AttributionIdInitializerTest.kt b/library/src/test/java/com/example/library/utils/AttributionIdInitializerTest.kt new file mode 100644 index 000000000..16cfad4a5 --- /dev/null +++ b/library/src/test/java/com/example/library/utils/AttributionIdInitializerTest.kt @@ -0,0 +1,62 @@ +/* + * 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.library.utils + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.example.library.utils.attribution.AttributionIdInitializer +import com.example.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/settings.gradle.kts b/settings.gradle.kts index af2968cd4..13b758669 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,3 +70,4 @@ project(":snippets:app-utils").projectDir = file("snippets/app-utils") include(":tutorials:kotlin:Polygons") project(":tutorials:kotlin:Polygons").projectDir = file("tutorials/kotlin/Polygons/app") // Add others as needed, starting with these for now +include(":library") diff --git a/snippets/app-compose/build.gradle.kts b/snippets/app-compose/build.gradle.kts index fb7d3134b..ef84a9f29 100644 --- a/snippets/app-compose/build.gradle.kts +++ b/snippets/app-compose/build.gradle.kts @@ -84,6 +84,7 @@ dependencies { implementation(libs.navigation.ui.ktx) implementation(libs.volley) implementation(libs.lifecycle.runtime.ktx) + implementation(project(":library")) // [END_EXCLUDE] // Android Maps Compose composables for the Maps SDK for Android diff --git a/snippets/app-ktx/build.gradle.kts b/snippets/app-ktx/build.gradle.kts index 143020ada..10736eccf 100644 --- a/snippets/app-ktx/build.gradle.kts +++ b/snippets/app-ktx/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { implementation(libs.core.ktx) implementation(libs.appcompat) implementation(libs.lifecycle.runtime.ktx) + implementation(project(":library")) // [END_EXCLUDE] // KTX for the Maps SDK for Android library diff --git a/snippets/app-places-ktx/build.gradle.kts b/snippets/app-places-ktx/build.gradle.kts index 321fce956..02ff4e78b 100644 --- a/snippets/app-places-ktx/build.gradle.kts +++ b/snippets/app-places-ktx/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) + implementation(project(":library")) // [END_EXCLUDE] // KTX for the Places SDK for Android library diff --git a/snippets/app-utils-ktx/build.gradle.kts b/snippets/app-utils-ktx/build.gradle.kts index dc93bcaf8..aaa298cff 100644 --- a/snippets/app-utils-ktx/build.gradle.kts +++ b/snippets/app-utils-ktx/build.gradle.kts @@ -26,7 +26,7 @@ android { defaultConfig { applicationId = "com.example.app_utils_ktx" - minSdk = 23 + minSdk = libs.versions.minSdk.get().toInt() targetSdk = 36 versionCode = 1 versionName = libs.versions.versionName.get() @@ -78,6 +78,7 @@ dependencies { implementation(libs.core.ktx) implementation(libs.appcompat) implementation(libs.lifecycle.runtime.ktx) + implementation(project(":library")) // [END_EXCLUDE] // KTX for the Maps SDK for Android Utility Library diff --git a/snippets/app-utils/build.gradle.kts b/snippets/app-utils/build.gradle.kts index 967da3892..8d2f0569b 100644 --- a/snippets/app-utils/build.gradle.kts +++ b/snippets/app-utils/build.gradle.kts @@ -70,7 +70,6 @@ android { // [START maps_android_utils_install_snippet] dependencies { - // [START_EXCLUDE silent] implementation(libs.core.ktx) implementation(libs.appcompat) @@ -78,6 +77,7 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) + implementation(project(":library")) // [END_EXCLUDE] // Utility Library for Maps SDK for Android diff --git a/snippets/app/build.gradle.kts b/snippets/app/build.gradle.kts index 940375905..5e46712c9 100644 --- a/snippets/app/build.gradle.kts +++ b/snippets/app/build.gradle.kts @@ -88,6 +88,7 @@ dependencies { implementation(libs.volley) implementation(libs.lifecycle.runtime.ktx) implementation(libs.places) + implementation(project(":library")) // [END_EXCLUDE] // Maps SDK for Android diff --git a/tutorials/kotlin/Polygons/app/build.gradle.kts b/tutorials/kotlin/Polygons/app/build.gradle.kts index 0078e4151..bcbf3e73d 100644 --- a/tutorials/kotlin/Polygons/app/build.gradle.kts +++ b/tutorials/kotlin/Polygons/app/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { implementation(libs.play.services.maps) implementation(libs.core.ktx) implementation(libs.kotlin.stdlib) + implementation(project(":library")) // Tests testImplementation(libs.junit)