diff --git a/ApiDemos/project/common-ui/build.gradle.kts b/ApiDemos/project/common-ui/build.gradle.kts index 595680348..164ea58ae 100644 --- a/ApiDemos/project/common-ui/build.gradle.kts +++ b/ApiDemos/project/common-ui/build.gradle.kts @@ -64,7 +64,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 d54da0788..59f0ab590 100644 --- a/FireMarkers/app/build.gradle.kts +++ b/FireMarkers/app/build.gradle.kts @@ -94,6 +94,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 7a6596fdb..77d5afadd 100644 --- a/WearOS/Wearable/build.gradle.kts +++ b/WearOS/Wearable/build.gradle.kts @@ -56,6 +56,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 189e0dc0c..0860c13a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -59,6 +59,7 @@ robolectric = "4.16.1" truth = "1.4.5" turbine = "1.2.1" uiautomator = "2.4.0" +mockk = "1.14.11" # Firebase firebaseBom = "34.17.0" @@ -68,12 +69,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" } @@ -132,6 +137,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..09e4b28a0 --- /dev/null +++ b/library/build.gradle.kts @@ -0,0 +1,116 @@ +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) +} + +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 129c8e19d..e1e8ed32e 100644 --- a/snippets/app-compose/build.gradle.kts +++ b/snippets/app-compose/build.gradle.kts @@ -83,6 +83,7 @@ dependencies { implementation(libs.navigation.ui.ktx) implementation(libs.volley) implementation(libs.lifecycle.runtime.ktx) + implementation(project(":library")) // [END_EXCLUDE] // Modern Android projects use version catalogs to manage dependencies. To include the Maps Compose library, diff --git a/snippets/app-ktx/build.gradle.kts b/snippets/app-ktx/build.gradle.kts index e305a70b0..81c7f48e4 100644 --- a/snippets/app-ktx/build.gradle.kts +++ b/snippets/app-ktx/build.gradle.kts @@ -77,6 +77,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 1076d3b21..72a8f4abd 100644 --- a/snippets/app-places-ktx/build.gradle.kts +++ b/snippets/app-places-ktx/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) + implementation(project(":library")) // [END_EXCLUDE] // Modern Android projects use version catalogs to manage dependencies. To include the Places SDK for Android, diff --git a/snippets/app-utils-ktx/build.gradle.kts b/snippets/app-utils-ktx/build.gradle.kts index 3ef852ef1..80cd57fe7 100644 --- a/snippets/app-utils-ktx/build.gradle.kts +++ b/snippets/app-utils-ktx/build.gradle.kts @@ -25,7 +25,7 @@ android { defaultConfig { applicationId = "com.example.app_utils_ktx" - minSdk = 23 + minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 versionName = libs.versions.versionName.get() @@ -77,6 +77,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 @@ -94,4 +95,4 @@ secrets { // A properties file containing default secret values. This file can be // checked in version control. defaultPropertiesFileName = "local.defaults.properties" -} \ No newline at end of file +} diff --git a/snippets/app-utils/build.gradle.kts b/snippets/app-utils/build.gradle.kts index 4f2da83c9..d0615f410 100644 --- a/snippets/app-utils/build.gradle.kts +++ b/snippets/app-utils/build.gradle.kts @@ -69,7 +69,6 @@ android { // [START maps_android_utils_install_snippet] dependencies { - // [START_EXCLUDE silent] implementation(libs.core.ktx) implementation(libs.appcompat) @@ -77,6 +76,7 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) + implementation(project(":library")) // [END_EXCLUDE] // Modern Android projects use version catalogs to manage dependencies. To include the Maps Utility library, diff --git a/snippets/app/build.gradle.kts b/snippets/app/build.gradle.kts index bf8744be5..60476045e 100644 --- a/snippets/app/build.gradle.kts +++ b/snippets/app/build.gradle.kts @@ -87,6 +87,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 d326d26ff..a149fec4b 100644 --- a/tutorials/kotlin/Polygons/app/build.gradle.kts +++ b/tutorials/kotlin/Polygons/app/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { implementation(libs.play.services.maps) implementation(libs.core.ktx) implementation(libs.kotlin.stdlib) + implementation(project(":library")) // Tests testImplementation(libs.junit)