Skip to content

Commit 6267545

Browse files
committed
feat(catalog): comprehensive 2D Maps SDK capabilities catalog and visual testing
Achieve 100% Core Maps SDK for Android programmatic capability parity with official index (capabilities.json). Key changes: - Migrate snippets into clean kotlin-app and java-app hierarchical snippet structures - Implement reflection-based SnippetRegistry with @snippetgroup and @SnippetItem annotations - Add DataDrivenBoundarySnippets and DatasetLayerSnippets covering Cloud DDS capabilities - Add CloudCustomizationSnippets demonstrating Cloud Console styling workflows loaded via Map ID - Add StreetViewSnippets registering panorama initialization, gestures, and camera animations - Add setMapColorScheme and enableTrafficLayer snippets to MapInitSnippets - Add instrumented test verification suite in SnippetDiscoveryTest - Add Gemini visual test automation framework capturing 25% scaled device screenshots - Add automated catalog generator (test/verify_catalog.py) maintaining CATALOG.md and COVERAGE.md
1 parent e6ead25 commit 6267545

276 files changed

Lines changed: 9723 additions & 7369 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,11 @@ secrets.properties
99

1010
# This covers new IDEs, like Antigravity
1111
.vscode/
12-
**/bin/
12+
**/bin/
13+
14+
# Temporary verification screenshot files
15+
*_screen*.png
16+
sydney_final.png
17+
*_sydney.png
18+
*_verified.png
19+
!**/assets/screenshots/*.png

check_api_key.gradle.kts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import java.util.Properties
2+
import org.gradle.api.GradleException
3+
4+
/**
5+
* Standalone API Key & Secrets Enforcement Script
6+
*
7+
* Checks for a valid Google Maps API key (starts with 'AIza') before executing build tasks.
8+
* Designed to be imported on-demand into application modules.
9+
*/
10+
11+
val secretsFile = rootProject.file("secrets.properties")
12+
val isCI = System.getenv("CI")?.toBoolean() ?: false
13+
14+
if (!isCI) {
15+
val requestedTasks = gradle.startParameter.taskNames
16+
17+
// 1. Allow Android Studio IDE sync (empty task invocation) to complete successfully
18+
if (requestedTasks.isEmpty() && !secretsFile.exists()) {
19+
println("⚠️ Warning: secrets.properties not found. Gradle sync will succeed, but building/running the app will fail.")
20+
} else if (requestedTasks.isNotEmpty()) {
21+
22+
// 2. Identify if the current invocation builds or installs the application
23+
val buildTaskKeywords = listOf("build", "install", "assemble", "bundle")
24+
val isBuildTask = requestedTasks.any { task ->
25+
buildTaskKeywords.any { keyword -> task.contains(keyword, ignoreCase = true) }
26+
}
27+
28+
// 3. Allow pure verification runs (unit tests, static analysis) to proceed without keys
29+
val testTaskKeywords = listOf("test", "report", "lint")
30+
val isTestTask = requestedTasks.any { task ->
31+
testTaskKeywords.any { keyword -> task.contains(keyword, ignoreCase = true) }
32+
}
33+
34+
if (isBuildTask && !isTestTask) {
35+
val defaultsFile = rootProject.file("local.defaults.properties")
36+
val requiredKeysMessage = if (defaultsFile.exists()) {
37+
defaultsFile.readText()
38+
} else {
39+
"MAPS_API_KEY=<YOUR_API_KEY>"
40+
}
41+
42+
if (!secretsFile.exists()) {
43+
throw GradleException(
44+
"Build Blocked: 'secrets.properties' file not found.\n" +
45+
"Please create 'secrets.properties' in the root project directory with the following content:\n\n" +
46+
requiredKeysMessage
47+
)
48+
}
49+
50+
// 4. Validate key integrity via Properties and Regex checking
51+
val secrets = Properties()
52+
secretsFile.inputStream().use { secrets.load(it) }
53+
54+
// Check for relevant key names (e.g., MAPS_API_KEY or MAPS3D_API_KEY)
55+
val apiKey = secrets.getProperty("MAPS_API_KEY") ?: secrets.getProperty("MAPS3D_API_KEY") ?: ""
56+
println("Checking API Key in secrets.properties: '$apiKey'")
57+
58+
if (apiKey.isBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
59+
throw GradleException(
60+
"Build Blocked: Invalid or missing Google Maps API key in 'secrets.properties'.\n" +
61+
"Please provide a valid API key starting with 'AIza'."
62+
)
63+
}
64+
}
65+
}
66+
}

gradle/libs.versions.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ materialIconsExtended = "1.7.8"
3838
# Google Maps & Places
3939
mapsCompose = "7.0.0"
4040
mapsKtx = "6.0.0"
41+
mapsUtils = "3.19.0"
4142
places = "5.1.1"
4243
playServicesMaps = "20.0.0"
4344
secretsGradlePlugin = "2.0.1"
@@ -66,6 +67,8 @@ firebaseDatabase = "22.0.1"
6667
# Third Party
6768
easypermissions = "3.0.0"
6869
volley = "1.2.1"
70+
ktor = "3.4.2"
71+
kotlinxSerialization = "1.11.0"
6972

7073
[libraries]
7174
# Kotlin
@@ -102,6 +105,7 @@ ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", versio
102105
# Maps & Places
103106
maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" }
104107
maps-ktx = { group = "com.google.maps.android", name = "maps-ktx", version.ref = "mapsKtx" }
108+
maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "mapsUtils" }
105109
maps-utils-ktx = { group = "com.google.maps.android", name = "maps-utils-ktx", version.ref = "mapsKtx" }
106110
places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" }
107111
play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" }
@@ -138,6 +142,11 @@ firebase-database = { module = "com.google.firebase:firebase-database", version.
138142
# Third Party
139143
easypermissions = { group = "pub.devrel", name = "easypermissions", version.ref = "easypermissions" }
140144
volley = { group = "com.android.volley", name = "volley", version.ref = "volley" }
145+
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
146+
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
147+
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
148+
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
149+
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
141150

142151
[plugins]
143152
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }

local.defaults.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ BOULDER_DATASET_ID=BOULDER_DATASET_ID
33
NEW_YORK_DATASET_ID=NEW_YORK_DATASET_ID
44
KYOTO_DATASET_ID=KYOTO_DATASET_ID
55
MAP_ID=MAP_ID
6+
GEMINI_API_KEY=YOUR_GEMINI_API_KEY

settings.gradle.kts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,23 @@ include(":WearOS:Wearable")
5353
project(":WearOS:Wearable").projectDir = file("WearOS/Wearable")
5454

5555
// Snippets
56-
include(":snippets:app")
57-
project(":snippets:app").projectDir = file("snippets/app")
58-
include(":snippets:app-ktx")
59-
project(":snippets:app-ktx").projectDir = file("snippets/app-ktx")
60-
include(":snippets:app-utils-ktx")
61-
project(":snippets:app-utils-ktx").projectDir = file("snippets/app-utils-ktx")
56+
include(":snippets:common")
57+
project(":snippets:common").projectDir = file("snippets/common")
58+
include(":snippets:java-app")
59+
project(":snippets:java-app").projectDir = file("snippets/java-app")
60+
include(":snippets:kotlin-app")
61+
project(":snippets:kotlin-app").projectDir = file("snippets/kotlin-app")
6262
include(":snippets:app-compose")
6363
project(":snippets:app-compose").projectDir = file("snippets/app-compose")
6464
include(":snippets:app-places-ktx")
6565
project(":snippets:app-places-ktx").projectDir = file("snippets/app-places-ktx")
66-
include(":snippets:app-utils")
67-
project(":snippets:app-utils").projectDir = file("snippets/app-utils")
6866

6967
// Tutorials
7068
include(":tutorials:kotlin:Polygons")
7169
project(":tutorials:kotlin:Polygons").projectDir = file("tutorials/kotlin/Polygons/app")
7270
// Add others as needed, starting with these for now
71+
72+
// Visual Testing
73+
include(":visual-testing")
74+
project(":visual-testing").projectDir = file("visual-testing")
75+

0 commit comments

Comments
 (0)