diff --git a/Maps3DSamples/ApiDemos/common/src/main/res/layout/main.xml b/Maps3DSamples/ApiDemos/common/src/main/res/layout/main.xml index 6540f7e..deda209 100644 --- a/Maps3DSamples/ApiDemos/common/src/main/res/layout/main.xml +++ b/Maps3DSamples/ApiDemos/common/src/main/res/layout/main.xml @@ -1,4 +1,20 @@ + + + + diff --git a/snippets/common/src/main/res/values/strings.xml b/snippets/common/src/main/res/values/strings.xml new file mode 100644 index 0000000..3bed536 --- /dev/null +++ b/snippets/common/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + Snippets + Hello Popover! + Info + Loading Map... + diff --git a/snippets/gradle.properties b/snippets/gradle.properties new file mode 100644 index 0000000..e892c3b --- /dev/null +++ b/snippets/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX=true +org.gradle.jvmargs=-Xmx4g diff --git a/snippets/gradle/libs.versions.toml b/snippets/gradle/libs.versions.toml new file mode 100644 index 0000000..4afb7dd --- /dev/null +++ b/snippets/gradle/libs.versions.toml @@ -0,0 +1,55 @@ +[versions] +compileSdk = "36" +minSdk = "26" +targetSdk = "36" + +activityCompose = "1.12.4" +agp = "8.13.2" +appcompat = "1.7.1" +composeBom = "2026.02.01" +coreKtx = "1.17.0" +desugar_jdk_libs = "2.1.5" +espressoCore = "3.7.0" +junit = "4.13.2" +junitVersion = "1.3.0" +kotlin = "2.3.0" +lifecycleRuntimeKtx = "2.10.0" +material = "1.13.0" + +playServicesBase = "18.10.0" +playServicesMaps3d = "0.2.0" +secretsGradlePlugin = "2.0.1" +truth = "1.4.5" +uiautomator = "2.3.0" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activityCompose" } +androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityCompose" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +play-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "playServicesBase" } +play-services-maps3d = { module = "com.google.android.gms:play-services-maps3d", version.ref = "playServicesMaps3d" } +truth = { module = "com.google.truth:truth", version.ref = "truth" } +androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } diff --git a/snippets/gradle/wrapper/gradle-wrapper.jar b/snippets/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8bdaf60 Binary files /dev/null and b/snippets/gradle/wrapper/gradle-wrapper.jar differ diff --git a/snippets/gradle/wrapper/gradle-wrapper.properties b/snippets/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6a38a8c --- /dev/null +++ b/snippets/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/snippets/gradlew b/snippets/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/snippets/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/snippets/gradlew.bat b/snippets/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/snippets/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/snippets/java-app/build.gradle.kts b/snippets/java-app/build.gradle.kts new file mode 100644 index 0000000..fea1ec6 --- /dev/null +++ b/snippets/java-app/build.gradle.kts @@ -0,0 +1,87 @@ +/* + * 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.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.secrets.gradle.plugin) +} + +android { + namespace = "com.example.snippets.java" + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + applicationId = "com.example.snippets.java" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } + } + buildFeatures { + buildConfig = true + } +} + +dependencies { + coreLibraryDesugaring(libs.desugar.jdk.libs) + + implementation(project(":common")) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity) + implementation(libs.material) + + implementation(libs.play.services.maps3d) + implementation(libs.play.services.base) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} + +secrets { + propertiesFileName = "secrets.properties" + defaultPropertiesFileName = "local.defaults.properties" +} + +tasks.register("installAndLaunchDebug") { + dependsOn("installDebug") + commandLine("adb", "shell", "am", "start", "-n", "com.example.snippets.java/.JavaSnippetsActivity") +} diff --git a/snippets/java-app/src/androidTest/java/com/example/snippets/java/ExampleInstrumentedTest.java b/snippets/java-app/src/androidTest/java/com/example/snippets/java/ExampleInstrumentedTest.java new file mode 100644 index 0000000..1b6bbad --- /dev/null +++ b/snippets/java-app/src/androidTest/java/com/example/snippets/java/ExampleInstrumentedTest.java @@ -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.snippets.java; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Rule + public ActivityScenarioRule activityRule = + new ActivityScenarioRule<>(MainActivity.class); + + @Test + public void useAppContext() { + // Context of the app under test. + String packageName = InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName(); + assertEquals("com.example.snippets.java", packageName); + } +} diff --git a/snippets/java-app/src/androidTest/java/com/example/snippets/java/MapActivityTest.java b/snippets/java-app/src/androidTest/java/com/example/snippets/java/MapActivityTest.java new file mode 100644 index 0000000..bb56452 --- /dev/null +++ b/snippets/java-app/src/androidTest/java/com/example/snippets/java/MapActivityTest.java @@ -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.snippets.java; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import com.example.snippets.java.MapActivity; // Implicitly imported if in same package but good to be explicit or if names collide + +@RunWith(AndroidJUnit4.class) +public class MapActivityTest { + + @Test + public void mapActivityLaunches() { + try (ActivityScenario scenario = ActivityScenario.launch(MapActivity.class)) { + // Check if map view is displayed + // R.id.map is in com.example.snippets.common.R + onView(withId(com.example.snippets.common.R.id.map)).check(matches(isDisplayed())); + } + } +} diff --git a/snippets/java-app/src/androidTest/java/com/example/snippets/java/SnippetRunTest.java b/snippets/java-app/src/androidTest/java/com/example/snippets/java/SnippetRunTest.java new file mode 100644 index 0000000..bb86eea --- /dev/null +++ b/snippets/java-app/src/androidTest/java/com/example/snippets/java/SnippetRunTest.java @@ -0,0 +1,65 @@ +/* + * 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.snippets.java; + +import android.content.Context; +import android.content.Intent; +import androidx.test.core.app.ActivityScenario; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import com.google.android.gms.maps3d.Map3DView; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.Set; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotNull; + +@RunWith(AndroidJUnit4.class) +public class SnippetRunTest { + + @Test + public void verifyAllSnippetsLaunchWithoutCrash() { + Set snippets = SnippetRegistry.snippets.keySet(); + Context context = ApplicationProvider.getApplicationContext(); + + for (String snippetTitle : snippets) { + Intent intent = new Intent(context, MapActivity.class); + intent.putExtra(MapActivity.EXTRA_SNIPPET_TITLE, snippetTitle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + try (ActivityScenario scenario = ActivityScenario.launch(intent)) { + CountDownLatch latch = new CountDownLatch(1); + scenario.onActivity(activity -> { + // Verify map view exists + Map3DView map = activity.findViewById(com.example.snippets.common.R.id.map); + if (map != null) { + latch.countDown(); + } + }); + if (!latch.await(5, TimeUnit.SECONDS)) { + fail("Map3DView not found or activity timeout for snippet: " + snippetTitle); + } + } catch (Exception e) { + fail("Crash executing snippet '" + snippetTitle + "': " + e.getMessage()); + } + } + } +} diff --git a/snippets/java-app/src/main/AndroidManifest.xml b/snippets/java-app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d6b6997 --- /dev/null +++ b/snippets/java-app/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/JavaSnippetsActivity.java b/snippets/java-app/src/main/java/com/example/snippets/java/JavaSnippetsActivity.java new file mode 100644 index 0000000..ba27d9f --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/JavaSnippetsActivity.java @@ -0,0 +1,58 @@ +/* + * 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.snippets.java; + +import android.content.Intent; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; +import java.util.List; + +import androidx.activity.EdgeToEdge; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +public class JavaSnippetsActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_main); + + RecyclerView recyclerView = findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + ViewCompat.setOnApplyWindowInsetsListener(recyclerView, (v, windowInsets) -> { + Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(insets.left, insets.top, insets.right, insets.bottom); + return WindowInsetsCompat.CONSUMED; + }); + + List snippets = new ArrayList<>(SnippetRegistry.snippets.values()); + + SnippetAdapter adapter = new SnippetAdapter(snippets, snippet -> { + Intent intent = new Intent(this, MapActivity.class); + intent.putExtra(MapActivity.EXTRA_SNIPPET_TITLE, snippet.title); + startActivity(intent); + }); + recyclerView.setAdapter(adapter); + } +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/MapActivity.java b/snippets/java-app/src/main/java/com/example/snippets/java/MapActivity.java new file mode 100644 index 0000000..1306247 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/MapActivity.java @@ -0,0 +1,142 @@ +/* + * 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.snippets.java; + +import android.os.Bundle; +import androidx.activity.EdgeToEdge; + +import android.util.Log; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import com.google.android.gms.maps3d.GoogleMap3D; + +import com.google.android.gms.maps3d.OnMap3DViewReadyCallback; + +import com.example.snippets.common.R; + +public class MapActivity extends AppCompatActivity { + + public static final String EXTRA_SNIPPET_TITLE = "snippet_title"; + + private com.google.android.gms.maps3d.Map3DView map3DView; + + private boolean triggered = false; + + private String snippetTitle; + private GoogleMap3D map; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_map); + map3DView = findViewById(R.id.map); + map3DView.onCreate(savedInstanceState); + + snippetTitle = getIntent().getStringExtra(EXTRA_SNIPPET_TITLE); + map3DView.getMap3DViewAsync(new OnMap3DViewReadyCallback() { + @Override + public void onMap3DViewReady(@NonNull GoogleMap3D map) { + MapActivity.this.map = map; + Log.w("MapActivity", "onMap3DViewReady" + map); + + runSnippet(); // <-- This will fail if the map is not ready + + map.setOnMapReadyListener(sceneReadiness -> { + if (!triggered && sceneReadiness >= 99.0) { + Log.w("MapActivity", "onMapReady sceneReadiness: " + sceneReadiness); + triggered = true; + runSnippet(); // <-- This will only be called the first time the map is ready + } + }); + } + + @Override + public void onError(@NonNull Exception error) { + runOnUiThread(() -> Toast.makeText(MapActivity.this, "Map Error: " + error.getMessage(), Toast.LENGTH_LONG).show()); + } + }); + } + + protected void runSnippet() { + // Map is ready + if (snippetTitle != null) { + Snippet snippet = SnippetRegistry.snippets.get(snippetTitle); + if (snippet != null) { + try { + snippet.action.execute(MapActivity.this, map); + runOnUiThread(() -> Toast.makeText(MapActivity.this, "Running: " + snippetTitle, Toast.LENGTH_SHORT).show()); + } catch (Exception e) { + runOnUiThread(() -> Toast.makeText(MapActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show()); + e.printStackTrace(); + } + } else { + runOnUiThread(() -> Toast.makeText(MapActivity.this, "Snippet not found: " + snippetTitle, Toast.LENGTH_LONG).show()); + } + } + } + + @Override + protected void onStart() { + super.onStart(); + if (map3DView != null) + map3DView.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + if (map3DView != null) + map3DView.onResume(); + } + + @Override + protected void onPause() { + if (map3DView != null) + map3DView.onPause(); + super.onPause(); + } + + @Override + protected void onStop() { + if (map3DView != null) + map3DView.onStop(); + super.onStop(); + } + + @Override + protected void onDestroy() { + if (map3DView != null) + map3DView.onDestroy(); + super.onDestroy(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + if (map3DView != null) + map3DView.onLowMemory(); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + if (map3DView != null) + map3DView.onSaveInstanceState(outState); + } +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/Snippet.java b/snippets/java-app/src/main/java/com/example/snippets/java/Snippet.java new file mode 100644 index 0000000..4929f4a --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/Snippet.java @@ -0,0 +1,29 @@ +/* + * 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.snippets.java; + +public class Snippet { + public final String title; + public final String description; + public final SnippetAction action; + + public Snippet(String title, String description, SnippetAction action) { + this.title = title; + this.description = description; + this.action = action; + } +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/SnippetAction.java b/snippets/java-app/src/main/java/com/example/snippets/java/SnippetAction.java new file mode 100644 index 0000000..e5b5a91 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/SnippetAction.java @@ -0,0 +1,24 @@ +/* + * 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.snippets.java; + +import android.content.Context; +import com.google.android.gms.maps3d.GoogleMap3D; + +public interface SnippetAction { + void execute(Context context, GoogleMap3D map); +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/SnippetAdapter.java b/snippets/java-app/src/main/java/com/example/snippets/java/SnippetAdapter.java new file mode 100644 index 0000000..15ea338 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/SnippetAdapter.java @@ -0,0 +1,75 @@ +/* + * 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.snippets.java; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; + +public class SnippetAdapter extends RecyclerView.Adapter { + + private final List snippets; + private final OnItemClickListener listener; + + public interface OnItemClickListener { + void onItemClick(Snippet snippet); + } + + public SnippetAdapter(List snippets, OnItemClickListener listener) { + this.snippets = snippets; + this.listener = listener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_snippet, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(snippets.get(position), listener); + } + + @Override + public int getItemCount() { + return snippets.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView title; + private final TextView description; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + title = itemView.findViewById(R.id.title); + description = itemView.findViewById(R.id.description); + } + + public void bind(final Snippet snippet, final OnItemClickListener listener) { + title.setText(snippet.title); + description.setText(snippet.description); + itemView.setOnClickListener(v -> listener.onItemClick(snippet)); + } + } +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/SnippetRegistry.java b/snippets/java-app/src/main/java/com/example/snippets/java/SnippetRegistry.java new file mode 100644 index 0000000..3ef15ea --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/SnippetRegistry.java @@ -0,0 +1,100 @@ +/* + * 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.snippets.java; + +import com.example.snippets.java.snippets.*; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class SnippetRegistry { + public static final Map snippets = new LinkedHashMap<>(); + + static { + registerSnippet("Map Initialization - Listeners", + "Initializes a 3D map and logs events when the scene is ready (100% loaded) and steady (camera stopped moving).", + (context, map) -> new MapInitSnippets().setupMapListeners(context, map)); + + registerSnippet("Camera - Fly To", + "Animates the camera to a specific position (Lat: 37.422, Lng: -122.084, Alt: 100m) with a 45-degree tilt and 90-degree heading over 5 seconds.", + (context, map) -> new CameraControlSnippets(map).flyCameraToPosition()); + + registerSnippet("Camera - Fly Around", + "Rotates the camera 360 degrees around a specific location (Lat: 37.422, Lng: -122.084) over 10 seconds.", + (context, map) -> new CameraControlSnippets(map).flyCameraAroundLocation()); + + registerSnippet("Camera - Stop Animation", + "Stops any currently running camera animation immediately.", + (context, map) -> new CameraControlSnippets(map).stopAnimation()); + + registerSnippet("Camera - Listen Events", + "Logs camera change events to the console, printing the center coordinates as the camera moves.", + (context, map) -> new CameraControlSnippets(map).listenToCameraEvents()); + + registerSnippet("Markers - Basic", + "Adds a standard marker at Lat: 37.422, Lng: -122.084, Alt: 10m.", + (context, map) -> new MarkerSnippets(map).addBasicMarker()); + + registerSnippet("Markers - Advanced", + "Adds a 'Priority Marker' at Lat: 37.422, Lng: -122.084, Alt: 10m (Relative to Ground) that is extruded and collides with other markers.", + (context, map) -> new MarkerSnippets(map).addAdvancedMarker()); + + registerSnippet("Markers - Click", + "Adds a marker at Lat: 37.42, Lng: -122.08 that logs a message when clicked.", + (context, map) -> new MarkerSnippets(map).handleMarkerClick()); + + registerSnippet("Polygons - Basic", + "Draws a red polygon with a blue stroke around a small area near Lat: 37.42, Lng: -122.08.", + (context, map) -> new PolygonSnippets(map).addBasicPolygon()); + + registerSnippet("Polygons - Extruded", + "Draws a semi-transparent red extruded polygon (height 50m) around a small area near Lat: 37.42, Lng: -122.08.", + (context, map) -> new PolygonSnippets(map).addExtrudedPolygon()); + + registerSnippet("Polylines - Basic", + "Draws a thick red polyline connecting three points near Lat: 37.42, Lng: -122.08.", + (context, map) -> new PolylineSnippets(map).addBasicPolyline()); + + registerSnippet("Polylines - Styled", + "Draws a magenta polyline with a green outline, extruded and following the ground curvature (geodesic), connecting two points.", + (context, map) -> new PolylineSnippets(map).addStyledPolyline()); + + registerSnippet("Models - Basic", + "Loads a GLB model from a URL (https://example.com/model.glb) and places it clamped to the ground at Lat: 37.422, Lng: -122.084.", + (context, map) -> new ModelSnippets(map).addBasicModel()); + + registerSnippet("Models - Advanced", + "Loads a GLB model from assets (my_model.glb), scales it by 2x, rotates it, and places it at 10m relative altitude.", + (context, map) -> new ModelSnippets(map).addAdvancedModel()); + + registerSnippet("Popovers - Marker", + "Adds a 'Hello Popover!' text bubble anchored to a marker at Lat: 37.422, Lng: -122.084.", + (context, map) -> new PopoverSnippets(context, map).addPopoverToMarker()); + + registerSnippet("Popovers - Configured", + "Adds an 'Info' popover anchored to a marker at [0,0] with auto-close enabled and auto-pan disabled.", + (context, map) -> new PopoverSnippets(context, map).addConfiguredPopover()); + + registerSnippet("Places - Listen Clicks", + "Sets up a listener that logs the Place ID when a user clicks on a 3D building or POI.", + (context, map) -> new PlaceSnippets(map).listenToPlaceClicks()); + } + + private static void registerSnippet(String title, String description, SnippetAction action) { + snippets.put(title, new Snippet(title, description, action)); + } +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/CameraControlSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/CameraControlSnippets.java new file mode 100644 index 0000000..04dee06 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/CameraControlSnippets.java @@ -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. + */ + +package com.example.snippets.java.snippets; + +import androidx.annotation.NonNull; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.OnCameraChangedListener; +import com.google.android.gms.maps3d.model.Camera; +import com.google.android.gms.maps3d.model.FlyAroundOptions; +import com.google.android.gms.maps3d.model.FlyToOptions; +import com.google.android.gms.maps3d.model.LatLngAltitude; + +public class CameraControlSnippets { + + private final GoogleMap3D map; + + public CameraControlSnippets(GoogleMap3D map) { + this.map = map; + } + + // [START maps_android_3d_camera_fly_to_java] + /** + * Animates the camera to a specific position (coordinates, heading, tilt) over + * a duration. + */ + public void flyCameraToPosition() { + LatLngAltitude center = new LatLngAltitude(37.4220, -122.0841, 100.0); + // Create a target camera: + // center: LatLngAltitude target + // heading: 90.0 (East) + // tilt: 45.0 degrees + // roll: 0.0 (level) + // range: 1000.0 meters + Camera targetCamera = new Camera(center, 90.0, 45.0, 0.0, 1000.0); + + // FlyToOptions constructor: endCamera, durationInMillis + FlyToOptions options = new FlyToOptions(targetCamera, 5000L); + + map.flyCameraTo(options); + } + // [END maps_android_3d_camera_fly_to_java] + + // [START maps_android_3d_camera_fly_around_java] + /** + * Orbits the camera around a specific location. + */ + public void flyCameraAroundLocation() { + LatLngAltitude center = new LatLngAltitude(37.4220, -122.0841, 0.0); + // Create a target camera: + // center: LatLngAltitude target + // heading: 0.0 (North) + // tilt: 45.0 degrees + // roll: 0.0 (level) + // range: 500.0 meters + Camera targetCamera = new Camera(center, 0.0, 45.0, 0.0, 500.0); + + // Orbit around the target + // FlyAroundOptions constructor: center, durationInMillis, rounds + FlyAroundOptions options = new FlyAroundOptions(targetCamera, 10000L, 1.0); + + map.flyCameraAround(options); + } + // [END maps_android_3d_camera_fly_around_java] + + // [START maps_android_3d_camera_stop_java] + /** + * Stops the current camera animation. + */ + public void stopAnimation() { + LatLngAltitude center = new LatLngAltitude(37.4220, -122.0841, 0.0); + Camera targetCamera = new Camera(center, 0.0, 45.0, 0.0, 500.0); + FlyAroundOptions options = new FlyAroundOptions(targetCamera, 30000L, 10.0); + + // 1. Start an animation so we have something to stop + map.flyCameraAround(options); + + // 2. Schedule the stop command after 3000ms (3 seconds) + new Handler(Looper.getMainLooper()).postDelayed(map::stopCameraAnimation, 3000); + } + // [END maps_android_3d_camera_stop_java] + + // [START maps_android_3d_camera_events_java] + // [START maps_android_3d_camera_events_java] + /** + * Listens to camera change events and logs the visible region. + */ + public void listenToCameraEvents() { + map.setCameraChangedListener(camera -> Log.d("Maps3D", "Camera State: " + + "Center: " + camera.getCenter() + + ", Heading: " + camera.getHeading() + + ", Tilt: " + camera.getTilt() + + ", Roll: " + camera.getRoll() + + ", Range: " + camera.getRange())); + } + // [END maps_android_3d_camera_events_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/MapInitSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/MapInitSnippets.java new file mode 100644 index 0000000..21cdb41 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/MapInitSnippets.java @@ -0,0 +1,90 @@ +/* + * 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.snippets.java.snippets; + +import android.content.Context; +import androidx.annotation.NonNull; +import com.google.android.gms.maps3d.GoogleMap3D; + +import com.google.android.gms.maps3d.Map3DView; +import com.google.android.gms.maps3d.OnMap3DViewReadyCallback; +import com.google.android.gms.maps3d.OnMapReadyListener; +import com.google.android.gms.maps3d.OnMapSteadyListener; +import com.google.android.gms.maps3d.model.Camera; +import com.google.android.gms.maps3d.model.LatLngAltitude; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; + +public class MapInitSnippets { + + // [START maps_android_3d_init_basic_java] + /** + * Initializes a standard 3D Map View. + */ + public void basicMap3D(Context context) { + Map3DView map3DView = new Map3DView(context); + + // Get the map asynchronously + map3DView.getMap3DViewAsync(new OnMap3DViewReadyCallback() { + @Override + public void onMap3DViewReady(@NonNull GoogleMap3D googleMap3D) { + // Map is ready to be used + LatLngAltitude center = new LatLngAltitude(40.0150, -105.2705, 5000.0); + Camera camera = new Camera(center, 0.0, 45.0, 0.0, 10000.0); + googleMap3D.setCamera(camera); + } + + @Override + public void onError(@NonNull Exception e) { + // Handle initialization error + } + }); + } + // [END maps_android_3d_init_basic_java] + + // [START maps_android_3d_init_listeners_java] + /** + * Sets up listeners for map readiness and steady state. + */ + public void setupMapListeners(Context context, GoogleMap3D map) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + + map.setOnMapReadyListener(new OnMapReadyListener() { + @Override + public void onMapReady(double sceneReadiness) { + if (sceneReadiness == 1.0) { + // Scene is fully loaded + mainHandler.post(() -> Toast + .makeText(context, "Map Scene Ready (100%)", Toast.LENGTH_SHORT).show()); + } + } + }); + + map.setOnMapSteadyListener(new OnMapSteadyListener() { + @Override + public void onMapSteadyChange(boolean isSceneSteady) { + if (isSceneSteady) { + // Camera is not moving and data is loaded + mainHandler.post(() -> Toast + .makeText(context, "Map Scene Steady", Toast.LENGTH_SHORT).show()); + } + } + }); + } + // [END maps_android_3d_init_listeners_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/MarkerSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/MarkerSnippets.java new file mode 100644 index 0000000..83d5d72 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/MarkerSnippets.java @@ -0,0 +1,150 @@ +/* + * 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.snippets.java.snippets; + +import androidx.annotation.NonNull; +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.model.AltitudeMode; +import com.google.android.gms.maps3d.model.CollisionBehavior; +import com.google.android.gms.maps3d.model.LatLngAltitude; +import com.google.android.gms.maps3d.model.Marker; +import com.google.android.gms.maps3d.model.MarkerOptions; +import com.google.android.gms.maps3d.model.PinConfiguration; +import com.google.android.gms.maps3d.model.Glyph; +import com.example.snippets.java.R; +import android.content.Context; +import android.graphics.Color; +import android.widget.ImageView; +import com.google.android.gms.maps3d.OnMarkerClickListener; + +public class MarkerSnippets { + + private final GoogleMap3D map; + + public MarkerSnippets(GoogleMap3D map) { + this.map = map; + } + + // [START maps_android_3d_marker_add_java] + /** + * Adds a basic marker to the map. + */ + public void addBasicMarker() { + LatLngAltitude position = new LatLngAltitude(37.4220, -122.0841, 10.0); + + MarkerOptions options = new MarkerOptions(); + options.setPosition(position); + options.setLabel("Basic Marker"); + // MarkerOptions uses label, not title. + + Marker marker = map.addMarker(options); + } + // [END maps_android_3d_marker_add_java] + + // [START maps_android_3d_marker_options_java] + /** + * Adds an advanced marker with detailed configuration options. + */ + public void addAdvancedMarker() { + LatLngAltitude position = new LatLngAltitude(37.4220, -122.0841, 10.0); + + MarkerOptions options = new MarkerOptions(); + options.setPosition(position); + options.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); + options.setLabel("Priority Marker"); + options.setCollisionBehavior(CollisionBehavior.REQUIRED); + options.setExtruded(true); + options.setDrawnWhenOccluded(true); + + Marker marker = map.addMarker(options); + } + // [END maps_android_3d_marker_options_java] + + // [START maps_android_3d_marker_click_java] + /** + * Adds a marker with a click listener. + */ + public void handleMarkerClick() { + LatLngAltitude position = new LatLngAltitude(37.42, -122.08, 0.0); + MarkerOptions options = new MarkerOptions(); + options.setPosition(position); + + Marker marker = map.addMarker(options); + + // [START_EXCLUDE] + if (marker != null) { + marker.setClickListener(new OnMarkerClickListener() { + @Override + public void onMarkerClick() { + // Handle click + } + }); + } + // [END_EXCLUDE] + } + // [END maps_android_3d_marker_click_java] + + // [START maps_android_3d_marker_custom_icon_java] + /** + * Adds a marker with a custom icon using PinConfiguration. + */ + public void addCustomMarker(Context context) { + LatLngAltitude position = new LatLngAltitude(37.4220, -122.0841, 10.0); + + // Create a Glyph with a custom image + Glyph glyphImage = Glyph.fromColor(Color.YELLOW); + ImageView imageView = new ImageView(context); + imageView.setImageResource(R.mipmap.ic_launcher); + + // Glyph.setImage expects a com.google.android.gms.maps3d.model.ImageView? No, wait. + // It likely expects an android.view.View or specialized type. + // Checking the Kotlin sample: glyphImage.setImage(ImageView(context)...) + // If the error says "incompatible types: android.widget.ImageView cannot be converted to com.google.android.gms.maps3d.model.ImageView", + // then the SDK has its own ImageView wrapper or I am importing the wrong one. + // Actually, looking at the hierarchy, Glyph.setImage might take a View, but if there is a name collision... + // Let's assume the SDK expects the Android ImageView but maybe the signature is weird? + // Wait, the error `com.google.android.gms.maps3d.model.ImageView` suggests there IS a model class named ImageView. + // I should check if I need to wrap it or use that class. + // But the Kotlin sample used `android.widget.ImageView`. + // Let's look at the error again: "actual type is 'android.widget.ImageView', but 'com.google.android.gms.maps3d.model.ImageView' was expected." + // This implies `Glyph` has a method `setImage(com.google.android.gms.maps3d.model.ImageView)`? + // Or maybe I am shadowing it? + + // Let's try to use the SDK's ImageView if it exists, or check imports. + // Actually, looking at the user provided code: `glyphImage.setImage(ImageView(R.drawable.ook))` + // The user code seems to use a constructor `ImageView(Int)`. Android's ImageView takes Context. + // So `com.google.android.gms.maps3d.model.ImageView` must be a thing! + + com.google.android.gms.maps3d.model.ImageView mapImageView = new com.google.android.gms.maps3d.model.ImageView(R.mipmap.ic_launcher); + glyphImage.setImage(mapImageView); + + MarkerOptions options = new MarkerOptions(); + options.setPosition(position); + options.setLabel("Custom Icon Marker"); + + // Set the style using PinConfiguration + options.setStyle(PinConfiguration.builder() + .setScale(1.5f) + .setGlyph(glyphImage) + .setBackgroundColor(Color.BLUE) + .setBorderColor(Color.WHITE) + .build()); + + Marker marker = map.addMarker(options); + } + // [END maps_android_3d_marker_custom_icon_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/ModelSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/ModelSnippets.java new file mode 100644 index 0000000..1fc87b8 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/ModelSnippets.java @@ -0,0 +1,68 @@ +/* + * 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.snippets.java.snippets; + +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.model.AltitudeMode; +import com.google.android.gms.maps3d.model.LatLngAltitude; +import com.google.android.gms.maps3d.model.Model; +import com.google.android.gms.maps3d.model.ModelOptions; +import com.google.android.gms.maps3d.model.Orientation; +import com.google.android.gms.maps3d.model.Vector3D; + +public class ModelSnippets { + + private final GoogleMap3D map; + + public ModelSnippets(GoogleMap3D map) { + this.map = map; + } + + // [START maps_android_3d_model_add_java] + /** + * Adds a basic 3D model (GLB) to the map from a URL. + */ + public void addBasicModel() { + LatLngAltitude position = new LatLngAltitude(37.4220, -122.0841, 0.0); + + ModelOptions options = new ModelOptions(); + options.setPosition(position); + options.setUrl("https://example.com/model.glb"); + options.setAltitudeMode(AltitudeMode.CLAMP_TO_GROUND); + + Model model = map.addModel(options); + } + // [END maps_android_3d_model_add_java] + + // [START maps_android_3d_model_options_java] + /** + * Adds a 3D model with advanced configuration (scale, orientation). + */ + public void addAdvancedModel() { + LatLngAltitude position = new LatLngAltitude(37.4220, -122.0841, 10.0); + + ModelOptions options = new ModelOptions(); + options.setPosition(position); + options.setUrl("file:///android_asset/my_model.glb"); + options.setScale(new Vector3D(2.0, 2.0, 2.0)); + options.setOrientation(new Orientation(0.0, 45.0, 0.0)); // heading, tilt, roll + options.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); + + Model model = map.addModel(options); + } + // [END maps_android_3d_model_options_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PlaceSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PlaceSnippets.java new file mode 100644 index 0000000..cb10cfe --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PlaceSnippets.java @@ -0,0 +1,48 @@ +/* + * 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.snippets.java.snippets; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.OnMap3DClickListener; +import com.google.android.gms.maps3d.model.LatLngAltitude; + +public class PlaceSnippets { + + private final GoogleMap3D map; + + public PlaceSnippets(GoogleMap3D map) { + this.map = map; + } + + // [START maps_android_3d_place_click_java] + /** + * Listens for clicks on 3D Places (buildings, POIs). + */ + public void listenToPlaceClicks() { + map.setMap3DClickListener(new OnMap3DClickListener() { + @Override + public void onMap3DClick(@NonNull LatLngAltitude location, @Nullable String placeId) { + if (placeId != null) { + // Handle place click + } + } + }); + } + // [END maps_android_3d_place_click_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PolygonSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PolygonSnippets.java new file mode 100644 index 0000000..d0ae712 --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PolygonSnippets.java @@ -0,0 +1,83 @@ +/* + * 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.snippets.java.snippets; + +import android.graphics.Color; +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.model.AltitudeMode; +import com.google.android.gms.maps3d.model.LatLngAltitude; +import com.google.android.gms.maps3d.model.Polygon; +import com.google.android.gms.maps3d.model.PolygonOptions; +import java.util.Arrays; +import java.util.List; + +public class PolygonSnippets { + + private final GoogleMap3D map; + + public PolygonSnippets(GoogleMap3D map) { + this.map = map; + } + + // [START maps_android_3d_polygon_add_java] + /** + * Adds a simple polygon to the map. + */ + public void addBasicPolygon() { + List points = Arrays.asList( + new LatLngAltitude(37.42, -122.08, 0.0), + new LatLngAltitude(37.42, -122.09, 0.0), + new LatLngAltitude(37.43, -122.09, 0.0), + new LatLngAltitude(37.43, -122.08, 0.0), + new LatLngAltitude(37.42, -122.08, 0.0) + ); + + PolygonOptions options = new PolygonOptions(); + options.setPath(points); + options.setFillColor(Color.RED); + options.setStrokeColor(Color.BLUE); + options.setStrokeWidth(5.0); + options.setAltitudeMode(AltitudeMode.CLAMP_TO_GROUND); + + Polygon polygon = map.addPolygon(options); + } + // [END maps_android_3d_polygon_add_java] + + // [START maps_android_3d_polygon_extruded_java] + /** + * Adds an extruded polygon with transparency. + */ + public void addExtrudedPolygon() { + List points = Arrays.asList( + new LatLngAltitude(37.42, -122.08, 50.0), + new LatLngAltitude(37.42, -122.09, 50.0), + new LatLngAltitude(37.43, -122.09, 50.0), + new LatLngAltitude(37.43, -122.08, 50.0), + new LatLngAltitude(37.42, -122.08, 50.0) + ); + + PolygonOptions options = new PolygonOptions(); + options.setPath(points); + options.setFillColor(0x88FF0000); // Semi-transparent red + options.setExtruded(true); + options.setGeodesic(true); + options.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); + + Polygon polygon = map.addPolygon(options); + } + // [END maps_android_3d_polygon_extruded_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PolylineSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PolylineSnippets.java new file mode 100644 index 0000000..a4a54ec --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PolylineSnippets.java @@ -0,0 +1,81 @@ +/* + * 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.snippets.java.snippets; + +import android.graphics.Color; +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.model.AltitudeMode; +import com.google.android.gms.maps3d.model.LatLngAltitude; +import com.google.android.gms.maps3d.model.Polyline; +import com.google.android.gms.maps3d.model.PolylineOptions; +import java.util.Arrays; +import java.util.List; + +public class PolylineSnippets { + + private final GoogleMap3D map; + + public PolylineSnippets(GoogleMap3D map) { + this.map = map; + } + + // [START maps_android_3d_polyline_add_java] + /** + * Adds a basic polyline to the map. + */ + public void addBasicPolyline() { + List points = Arrays.asList( + new LatLngAltitude(37.42, -122.08, 0.0), + new LatLngAltitude(37.43, -122.09, 0.0), + new LatLngAltitude(37.44, -122.08, 0.0) + ); + + PolylineOptions options = new PolylineOptions(); + options.setPath(points); + options.setStrokeColor(Color.RED); + options.setStrokeWidth(10.0); + options.setAltitudeMode(AltitudeMode.CLAMP_TO_GROUND); + + Polyline polyline = map.addPolyline(options); + } + // [END maps_android_3d_polyline_add_java] + + // [START maps_android_3d_polyline_options_java] + /** + * Adds a styled polyline with complex configuration. + */ + public void addStyledPolyline() { + List points = Arrays.asList( + new LatLngAltitude(37.42, -122.08, 50.0), + new LatLngAltitude(37.43, -122.09, 100.0) + ); + + PolylineOptions options = new PolylineOptions(); + options.setPath(points); + options.setStrokeColor(0xFFFF00FF); // Magenta + options.setStrokeWidth(20.0); + options.setOuterColor(0xFF00FF00); // Green + options.setOuterWidth(2.0); + options.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); + options.setExtruded(true); + options.setGeodesic(true); + options.setDrawsOccludedSegments(true); + + Polyline polyline = map.addPolyline(options); + } + // [END maps_android_3d_polyline_options_java] +} diff --git a/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PopoverSnippets.java b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PopoverSnippets.java new file mode 100644 index 0000000..641dcba --- /dev/null +++ b/snippets/java-app/src/main/java/com/example/snippets/java/snippets/PopoverSnippets.java @@ -0,0 +1,89 @@ +/* + * 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.snippets.java.snippets; + +import com.example.snippets.common.R; +import android.content.Context; +import android.graphics.Color; +import android.widget.TextView; +import com.google.android.gms.maps3d.GoogleMap3D; +import com.google.android.gms.maps3d.model.AltitudeMode; +import com.google.android.gms.maps3d.model.LatLngAltitude; +import com.google.android.gms.maps3d.model.Marker; +import com.google.android.gms.maps3d.model.MarkerOptions; +import com.google.android.gms.maps3d.Popover; +import com.google.android.gms.maps3d.model.PopoverOptions; + +public class PopoverSnippets { + + private final Context context; + private final GoogleMap3D map; + + public PopoverSnippets(Context context, GoogleMap3D map) { + this.context = context; + this.map = map; + } + + // [START maps_android_3d_popover_add_java] + /** + * Adds a popover anchored to a marker. + */ + public void addPopoverToMarker() { + // Create a marker first + Marker marker = map.addMarker(new MarkerOptions()); + if (marker == null) + return; + + // Create a custom view for the popover + TextView textView = new TextView(context); + textView.setText(R.string.popover_hello); + textView.setPadding(16, 16, 16, 16); + textView.setBackgroundColor(Color.WHITE); + + // Add popover anchored to the marker + PopoverOptions options = new PopoverOptions(); + options.setContent(textView); + options.setPositionAnchor(marker); + options.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); + + Popover popover = map.addPopover(options); + + // You can show/hide it + if (popover != null) { + popover.show(); + } + } + // [END maps_android_3d_popover_add_java] + + // [START maps_android_3d_popover_options_java] + /** + * Adds a configured popover (auto-close enabled, auto-pan disabled). + */ + public void addConfiguredPopover() { + TextView textView = new TextView(context); + textView.setText(com.example.snippets.common.R.string.popover_info); + + PopoverOptions options = new PopoverOptions(); + options.setContent(textView); + options.setPositionAnchor(new LatLngAltitude(0, 0, 0)); + options.setAutoCloseEnabled(true); // Close when clicking elsewhere + options.setAutoPanEnabled(false); // Do not pan to popover + + map.addPopover(options); + } + // [END maps_android_3d_popover_options_java] +} diff --git a/snippets/java-app/src/main/res/drawable/ic_launcher_background.xml b/snippets/java-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..7ba6a0f --- /dev/null +++ b/snippets/java-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/snippets/java-app/src/main/res/drawable/ic_launcher_foreground.xml b/snippets/java-app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..7451489 --- /dev/null +++ b/snippets/java-app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/snippets/java-app/src/main/res/layout/activity_main.xml b/snippets/java-app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..bf638fe --- /dev/null +++ b/snippets/java-app/src/main/res/layout/activity_main.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/snippets/java-app/src/main/res/layout/list_item_snippet.xml b/snippets/java-app/src/main/res/layout/list_item_snippet.xml new file mode 100644 index 0000000..299118d --- /dev/null +++ b/snippets/java-app/src/main/res/layout/list_item_snippet.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/snippets/java-app/src/main/res/mipmap-anydpi/ic_launcher.xml b/snippets/java-app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..fee4243 --- /dev/null +++ b/snippets/java-app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/snippets/java-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/snippets/java-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..fee4243 --- /dev/null +++ b/snippets/java-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/snippets/java-app/src/main/res/values/colors.xml b/snippets/java-app/src/main/res/values/colors.xml new file mode 100644 index 0000000..20e95a2 --- /dev/null +++ b/snippets/java-app/src/main/res/values/colors.xml @@ -0,0 +1,23 @@ + + + + + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FFFFFF + diff --git a/snippets/java-app/src/main/res/values/themes.xml b/snippets/java-app/src/main/res/values/themes.xml new file mode 100644 index 0000000..a6304a3 --- /dev/null +++ b/snippets/java-app/src/main/res/values/themes.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/snippets/kotlin-app/build.gradle.kts b/snippets/kotlin-app/build.gradle.kts new file mode 100644 index 0000000..4adb83e --- /dev/null +++ b/snippets/kotlin-app/build.gradle.kts @@ -0,0 +1,100 @@ +/* + * 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.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.secrets.gradle.plugin) +} + +android { + namespace = "com.example.snippets.kotlin" + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + applicationId = "com.example.snippets.kotlin" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } + } + buildFeatures { + compose = true + buildConfig = true + } +} + +dependencies { + coreLibraryDesugaring(libs.desugar.jdk.libs) + + implementation(project(":common")) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + + implementation(libs.play.services.maps3d) + implementation(libs.play.services.base) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} + +secrets { + propertiesFileName = "secrets.properties" + defaultPropertiesFileName = "local.defaults.properties" +} + +tasks.register("installAndLaunchDebug") { + dependsOn("installDebug") + commandLine("adb", "shell", "am", "start", "-n", "com.example.snippets.kotlin/.KotlinSnippetsActivity") +} diff --git a/snippets/kotlin-app/src/androidTest/java/com/example/snippets/kotlin/ExampleInstrumentedTest.kt b/snippets/kotlin-app/src/androidTest/java/com/example/snippets/kotlin/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..7fb2db7 --- /dev/null +++ b/snippets/kotlin-app/src/androidTest/java/com/example/snippets/kotlin/ExampleInstrumentedTest.kt @@ -0,0 +1,44 @@ +/* + * 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.snippets.kotlin + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.* +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + @Test + fun mainActivityLaunches() { + // Check if "Map Initialization" is displayed (substring because of description) + composeTestRule.waitForIdle() + try { + composeTestRule.onNodeWithText("Map Initialization", substring = true).assertExists() + } catch (e: AssertionError) { + println("Hierarchy check failed. Printing tree:") + composeTestRule.onRoot().printToLog("HierarchyTree") + throw e + } + } +} diff --git a/snippets/kotlin-app/src/androidTest/java/com/example/snippets/kotlin/SnippetDiscoveryTest.kt b/snippets/kotlin-app/src/androidTest/java/com/example/snippets/kotlin/SnippetDiscoveryTest.kt new file mode 100644 index 0000000..e453221 --- /dev/null +++ b/snippets/kotlin-app/src/androidTest/java/com/example/snippets/kotlin/SnippetDiscoveryTest.kt @@ -0,0 +1,64 @@ +/* + * 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.snippets.kotlin + +import android.content.Intent +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.gms.maps3d.Map3DView +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +@RunWith(AndroidJUnit4::class) +class SnippetDiscoveryTest { + + @Test + fun verifyAllSnippetsLaunchWithoutCrash() { + val snippets = SnippetRegistry.snippets.keys + val context = ApplicationProvider.getApplicationContext() + + for (snippetTitle in snippets) { + val intent = Intent(context, MapActivity::class.java).apply { + putExtra(MapActivity.EXTRA_SNIPPET_TITLE, snippetTitle) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + try { + ActivityScenario.launch(intent).use { scenario -> + val latch = CountDownLatch(1) + scenario.onActivity { activity -> + // Verify map view exists + val map = activity.findViewById(com.example.snippets.common.R.id.map) + if (map != null) { + latch.countDown() + } + } + if (!latch.await(5, TimeUnit.SECONDS)) { + fail("Map3DView not found or activity timeout for snippet: $snippetTitle") + } + } + } catch (e: Exception) { + fail("Crash executing snippet '$snippetTitle': ${e.message}") + } + } + } +} diff --git a/snippets/kotlin-app/src/main/AndroidManifest.xml b/snippets/kotlin-app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44a87af --- /dev/null +++ b/snippets/kotlin-app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/KotlinSnippetsActivity.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/KotlinSnippetsActivity.kt new file mode 100644 index 0000000..dcecc66 --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/KotlinSnippetsActivity.kt @@ -0,0 +1,103 @@ +/* + * 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.snippets.kotlin + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.snippets.kotlin.ui.theme.SnippetsTheme + +class KotlinSnippetsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + SnippetsTheme { + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold( + modifier = Modifier.fillMaxSize(), + snackbarHost = { SnackbarHost(hostState = snackbarHostState) } + ) { innerPadding -> + SnippetList( + snippets = SnippetRegistry.snippets.values.toList(), + contentPadding = innerPadding, + onItemClick = { snippet -> + val intent = Intent(this, MapActivity::class.java) + intent.putExtra(MapActivity.EXTRA_SNIPPET_TITLE, snippet.title) + startActivity(intent) + } + ) + } + } + } + } +} + +@Composable +fun SnippetList( + snippets: List, + modifier: Modifier = Modifier, + contentPadding: androidx.compose.foundation.layout.PaddingValues = androidx.compose.foundation.layout.PaddingValues(0.dp), + onItemClick: (Snippet) -> Unit +) { + LazyColumn( + modifier = modifier, + contentPadding = contentPadding + ) { + items(snippets) { snippet -> + Column( + modifier = Modifier + .fillMaxWidth() + .clickable { onItemClick(snippet) } + .padding(16.dp) + ) { + Text( + text = snippet.title, + style = MaterialTheme.typography.titleMedium + ) + Text( + text = snippet.description, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + HorizontalDivider() + } + } +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/MapActivity.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/MapActivity.kt new file mode 100644 index 0000000..eacab7e --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/MapActivity.kt @@ -0,0 +1,110 @@ +/* + * 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.snippets.kotlin + +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.example.snippets.common.R +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.Map3DView +import com.google.android.gms.maps3d.OnMap3DViewReadyCallback + +class MapActivity : AppCompatActivity() { + + companion object { + const val EXTRA_SNIPPET_TITLE = "snippet_title" + } + + private lateinit var map3DView: Map3DView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + androidx.core.view.WindowCompat.setDecorFitsSystemWindows(window, false) + setContentView(R.layout.activity_map) + + map3DView = findViewById(R.id.map) + map3DView.onCreate(savedInstanceState) + + val snippetTitle = intent.getStringExtra(EXTRA_SNIPPET_TITLE) + + map3DView.getMap3DViewAsync(object : OnMap3DViewReadyCallback { + override fun onMap3DViewReady(googleMap3D: GoogleMap3D) { + // For simplicity, we run immediately, but add a listener for logs + googleMap3D.setOnMapReadyListener { sceneReadiness -> + if (sceneReadiness == 1.0) { + // Toast.makeText(this@MapActivity, "Map Ready", Toast.LENGTH_SHORT).show() + } + } + + if (snippetTitle != null) { + val snippet = SnippetRegistry.snippets[snippetTitle] + if (snippet != null) { + try { + snippet.action(this@MapActivity, googleMap3D) + Toast.makeText(this@MapActivity, "Running: $snippetTitle", Toast.LENGTH_SHORT).show() + } catch (e: Exception) { + Toast.makeText(this@MapActivity, "Error: ${e.message}", Toast.LENGTH_LONG).show() + e.printStackTrace() + } + } else { + Toast.makeText(this@MapActivity, "Snippet not found: $snippetTitle", Toast.LENGTH_LONG).show() + } + } + } + + override fun onError(error: java.lang.Exception) { + Toast.makeText(this@MapActivity, "Map Error: ${error.message}", Toast.LENGTH_LONG).show() + } + }) + } + + override fun onStart() { + super.onStart() + map3DView.onStart() + } + + override fun onResume() { + super.onResume() + map3DView.onResume() + } + + override fun onPause() { + map3DView.onPause() + super.onPause() + } + + override fun onStop() { + map3DView.onStop() + super.onStop() + } + + override fun onDestroy() { + map3DView.onDestroy() + super.onDestroy() + } + + override fun onLowMemory() { + super.onLowMemory() + map3DView.onLowMemory() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + map3DView.onSaveInstanceState(outState) + } +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/SnippetRegistry.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/SnippetRegistry.kt new file mode 100644 index 0000000..c59e3aa --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/SnippetRegistry.kt @@ -0,0 +1,132 @@ +/* + * 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.snippets.kotlin + +import android.content.Context +import com.example.snippets.kotlin.snippets.CameraControlSnippets +import com.example.snippets.kotlin.snippets.MapInitSnippets +import com.example.snippets.kotlin.snippets.MarkerSnippets +import com.example.snippets.kotlin.snippets.ModelSnippets +import com.example.snippets.kotlin.snippets.PlaceSnippets +import com.example.snippets.kotlin.snippets.PolygonSnippets +import com.example.snippets.kotlin.snippets.PolylineSnippets +import com.example.snippets.kotlin.snippets.PopoverSnippets +import com.google.android.gms.maps3d.GoogleMap3D + + +data class Snippet( + val title: String, + val description: String, + val action: (Context, GoogleMap3D) -> Unit +) + +object SnippetRegistry { + val snippets = mapOf( + "Map Initialization - Listeners" to Snippet( + "Map Initialization - Listeners", + "Initializes a 3D map and logs events when the scene is ready (100% loaded) and steady (camera stopped moving).", + { context, map -> MapInitSnippets().setupMapListeners(context, map) } + ), + + "Camera - Fly To" to Snippet( + "Camera - Fly To", + "Animates the camera to a specific position (Lat: 37.422, Lng: -122.084, Alt: 100m) with a 45-degree tilt and 90-degree heading over 5 seconds.", + { _, map -> CameraControlSnippets(map).flyCameraToPosition() } + ), + "Camera - Fly Around" to Snippet( + "Camera - Fly Around", + "Rotates the camera 360 degrees around a specific location (Lat: 37.422, Lng: -122.084) over 10 seconds.", + { _, map -> CameraControlSnippets(map).flyCameraAroundLocation() } + ), + "Camera - Stop Animation" to Snippet( + "Camera - Stop Animation", + "Stops any currently running camera animation immediately.", + { _, map -> CameraControlSnippets(map).stopAnimation() } + ), + "Camera - Listen Events" to Snippet( + "Camera - Listen Events", + "Logs camera change events to the console, printing the center coordinates as the camera moves.", + { _, map -> CameraControlSnippets(map).listenToCameraEvents() } + ), + + "Markers - Basic" to Snippet( + "Markers - Basic", + "Adds a standard marker at Lat: 37.422, Lng: -122.084, Alt: 10m.", + { _, map -> MarkerSnippets(map).addBasicMarker() } + ), + "Markers - Advanced" to Snippet( + "Markers - Advanced", + "Adds a 'Priority Marker' at Lat: 37.422, Lng: -122.084, Alt: 10m (Relative to Ground) that is extruded and collides with other markers.", + { _, map -> MarkerSnippets(map).addAdvancedMarker() } + ), + "Markers - Click" to Snippet( + "Markers - Click", + "Adds a marker at Lat: 37.42, Lng: -122.08 that logs a message when clicked.", + { _, map -> MarkerSnippets(map).handleMarkerClick() } + ), + + "Polygons - Basic" to Snippet( + "Polygons - Basic", + "Draws a red polygon with a blue stroke around a small area near Lat: 37.42, Lng: -122.08.", + { _, map -> PolygonSnippets(map).addBasicPolygon() } + ), + "Polygons - Extruded" to Snippet( + "Polygons - Extruded", + "Draws a semi-transparent red extruded polygon (height 50m) around a small area near Lat: 37.42, Lng: -122.08.", + { _, map -> PolygonSnippets(map).addExtrudedPolygon() } + ), + + "Polylines - Basic" to Snippet( + "Polylines - Basic", + "Draws a thick red polyline connecting three points near Lat: 37.42, Lng: -122.08.", + { _, map -> PolylineSnippets(map).addBasicPolyline() } + ), + "Polylines - Styled" to Snippet( + "Polylines - Styled", + "Draws a magenta polyline with a green outline, extruded and following the ground curvature (geodesic), connecting two points.", + { _, map -> PolylineSnippets(map).addStyledPolyline() } + ), + + "Models - Basic" to Snippet( + "Models - Basic", + "Loads a GLB model from a URL (https://example.com/model.glb) and places it clamped to the ground at Lat: 37.422, Lng: -122.084.", + { _, map -> ModelSnippets(map).addBasicModel() } + ), + "Models - Advanced" to Snippet( + "Models - Advanced", + "Loads a GLB model from assets (my_model.glb), scales it by 2x, rotates it, and places it at 10m relative altitude.", + { _, map -> ModelSnippets(map).addAdvancedModel() } + ), + + "Popovers - Marker" to Snippet( + "Popovers - Marker", + "Adds a 'Hello Popover!' text bubble anchored to a marker at Lat: 37.422, Lng: -122.084.", + { context, map -> PopoverSnippets(context, map).addPopoverToMarker() } + ), + "Popovers - Configured" to Snippet( + "Popovers - Configured", + "Adds an 'Info' popover anchored to a marker at [0,0] with auto-close enabled and auto-pan disabled.", + { context, map -> PopoverSnippets(context, map).addConfiguredPopover() } + ), + + "Places - Listen Clicks" to Snippet( + "Places - Listen Clicks", + "Sets up a listener that logs the Place ID when a user clicks on a 3D building or POI.", + { _, map -> PlaceSnippets(map).listenToPlaceClicks() } + ) + ) +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/CameraControlSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/CameraControlSnippets.kt new file mode 100644 index 0000000..008fd8d --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/CameraControlSnippets.kt @@ -0,0 +1,101 @@ +/* + * 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.snippets.kotlin.snippets + +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.model.camera +import android.util.Log +import com.google.android.gms.maps3d.model.flyAroundOptions +import com.google.android.gms.maps3d.model.flyToOptions +import com.google.android.gms.maps3d.model.latLngAltitude + +class CameraControlSnippets(private val map: GoogleMap3D) { + + // [START maps_android_3d_camera_fly_to_kt] + /** + * Animates the camera to a specific position (coordinates, heading, tilt) over a duration. + */ + fun flyCameraToPosition() { + val targetCamera = camera { + center = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 100.0 + } + tilt = 45.0 + heading = 90.0 + } + + val options = flyToOptions { + endCamera = targetCamera + durationInMillis = 5000 + } + + map.flyCameraTo(options) + } + // [END maps_android_3d_camera_fly_to_kt] + + // [START maps_android_3d_camera_fly_around_kt] + /** + * Orbits the camera around a specific location. + */ + fun flyCameraAroundLocation() { + val targetCamera = camera { + center = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 0.0 + } + } + + // Orbit around the target using DSL + val options = flyAroundOptions { + center = targetCamera + rounds = 1.0 // 1 full rotation + durationInMillis = 10000 + } + + map.flyCameraAround(options) + } + // [END maps_android_3d_camera_fly_around_kt] + + // [START maps_android_3d_camera_stop_kt] + /** + * Stops the current camera animation. + */ + fun stopAnimation() { + map.stopCameraAnimation() + } + // [END maps_android_3d_camera_stop_kt] + + // [START maps_android_3d_camera_events_kt] + // [START maps_android_3d_camera_events_kt] + /** + * Listens to camera change events and logs the visible region. + */ + fun listenToCameraEvents() { + map.setCameraChangedListener { camera -> + Log.d("Maps3D", "Camera State: " + + "Center: ${camera.center}, " + + "Heading: ${camera.heading}, " + + "Tilt: ${camera.tilt}, " + + "Roll: ${camera.roll}, " + + "Range: ${camera.range}") + } + } + // [END maps_android_3d_camera_events_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/MapInitSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/MapInitSnippets.kt new file mode 100644 index 0000000..5650277 --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/MapInitSnippets.kt @@ -0,0 +1,97 @@ +/* + * 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.snippets.kotlin.snippets + +import androidx.compose.foundation.layout.fillMaxSize +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.widget.Toast +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.Map3DView +import com.google.android.gms.maps3d.OnMap3DViewReadyCallback +import com.google.android.gms.maps3d.model.camera +import com.google.android.gms.maps3d.model.latLngAltitude + + +class MapInitSnippets { + + // [START maps_android_3d_init_basic_kt] + /** + * Initializes a standard 3D Map View using AndroidView in Compose. + */ + @Composable + fun BasicMap3D() { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { context -> + Map3DView(context).apply { + getMap3DViewAsync(object : OnMap3DViewReadyCallback { + override fun onMap3DViewReady(googleMap3D: GoogleMap3D) { + val camera = camera { + center = latLngAltitude { + latitude = 40.0150 + longitude = -105.2705 + altitude = 5000.0 + } + heading = 0.0 + tilt = 45.0 + roll = 0.0 + range = 10000.0 + } + googleMap3D.setCamera(camera) + } + override fun onError(error: Exception) { + // Handle initialization error + } + }) + } + } + ) + } + // [END maps_android_3d_init_basic_kt] + + // [START maps_android_3d_init_listeners_kt] + /** + * Sets up listeners for map readiness and steady state. + */ + fun setupMapListeners(context: Context, map: GoogleMap3D) { + val mainHandler = Handler(Looper.getMainLooper()) + + map.setOnMapReadyListener { sceneReadiness -> + if (sceneReadiness == 1.0) { + // Scene is fully loaded + mainHandler.post { + Toast.makeText(context, "Map Scene Ready (100%)", Toast.LENGTH_SHORT).show() + } + } + } + + map.setOnMapSteadyListener { isSceneSteady -> + if (isSceneSteady) { + // Camera is not moving and data is loaded + mainHandler.post { + Toast.makeText(context, "Map Scene Steady", Toast.LENGTH_SHORT).show() + } + } + } + } + // [END maps_android_3d_init_listeners_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/MarkerSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/MarkerSnippets.kt new file mode 100644 index 0000000..b066ef4 --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/MarkerSnippets.kt @@ -0,0 +1,122 @@ +/* + * 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.snippets.kotlin.snippets + +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.model.AltitudeMode +import com.google.android.gms.maps3d.model.CollisionBehavior +import com.google.android.gms.maps3d.model.latLngAltitude +import com.google.android.gms.maps3d.model.markerOptions +import com.google.android.gms.maps3d.model.PinConfiguration +import com.google.android.gms.maps3d.model.Glyph +import com.example.snippets.kotlin.R +import android.content.Context +import android.graphics.Color +import android.widget.ImageView + +class MarkerSnippets(private val map: GoogleMap3D) { + + // [START maps_android_3d_marker_add_kt] + /** + * Adds a basic marker to the map. + */ + fun addBasicMarker() { + val position = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 10.0 + } + + val options = markerOptions { + this.position = position + label = "Basic Marker" + // MarkerOptions uses label, not title. + } + + val marker = map.addMarker(options) + } + // [END maps_android_3d_marker_add_kt] + + // [START maps_android_3d_marker_options_kt] + /** + * Adds an advanced marker with detailed configuration options. + */ + fun addAdvancedMarker() { + val options = markerOptions { + position = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 10.0 + } + altitudeMode = AltitudeMode.RELATIVE_TO_GROUND + label = "Priority Marker" + collisionBehavior = CollisionBehavior.REQUIRED + isExtruded = true + isDrawnWhenOccluded = true + } + + val marker = map.addMarker(options) + } + // [END maps_android_3d_marker_options_kt] + + // [START maps_android_3d_marker_click_kt] + /** + * Adds a marker with a click listener. + */ + fun handleMarkerClick() { + val marker = map.addMarker(markerOptions { + position = latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 0.0 } + }) + + // [START_EXCLUDE] + marker?.setClickListener { + // Handle click + } + // [END_EXCLUDE] + } + // [END maps_android_3d_marker_click_kt] + + // [START maps_android_3d_marker_custom_icon_kt] + /** + * Adds a marker with a custom icon using PinConfiguration. + */ + fun addCustomMarker(context: Context) { + // Create a Glyph with a custom image + val glyphImage = Glyph.fromColor(Color.YELLOW) + // Use the Maps SDK's ImageView, not the Android widget + glyphImage.setImage(com.google.android.gms.maps3d.model.ImageView(R.mipmap.ic_launcher)) + + val options = markerOptions { + position = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 10.0 + } + label = "Custom Icon Marker" + // Set the style using PinConfiguration + setStyle(PinConfiguration.builder() + .setScale(1.5f) + .setGlyph(glyphImage) + .setBackgroundColor(Color.BLUE) + .setBorderColor(Color.WHITE) + .build()) + } + + val marker = map.addMarker(options) + } + // [END maps_android_3d_marker_custom_icon_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/ModelSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/ModelSnippets.kt new file mode 100644 index 0000000..049d75e --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/ModelSnippets.kt @@ -0,0 +1,73 @@ +/* + * 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.snippets.kotlin.snippets + +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.model.AltitudeMode +import com.google.android.gms.maps3d.model.latLngAltitude +import com.google.android.gms.maps3d.model.modelOptions +import com.google.android.gms.maps3d.model.orientation +import com.google.android.gms.maps3d.model.vector3D + +class ModelSnippets(private val map: GoogleMap3D) { + + // [START maps_android_3d_model_add_kt] + /** + * Adds a basic 3D model (GLB) to the map from a URL. + */ + fun addBasicModel() { + val position = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 0.0 + } + + val options = modelOptions { + this.position = position + url = "https://example.com/model.glb" + altitudeMode = AltitudeMode.CLAMP_TO_GROUND + } + + val model = map.addModel(options) + } + // [END maps_android_3d_model_add_kt] + + // [START maps_android_3d_model_options_kt] + /** + * Adds a 3D model with advanced configuration (scale, orientation). + */ + fun addAdvancedModel() { + val options = modelOptions { + position = latLngAltitude { + latitude = 37.4220 + longitude = -122.0841 + altitude = 10.0 + } + url = "file:///android_asset/my_model.glb" + scale = vector3D { x = 2.0; y = 2.0; z = 2.0 } + orientation = orientation { + tilt = 45.0 + heading = 0.0 + roll = 0.0 + } + altitudeMode = AltitudeMode.RELATIVE_TO_GROUND + } + + val model = map.addModel(options) + } + // [END maps_android_3d_model_options_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PlaceSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PlaceSnippets.kt new file mode 100644 index 0000000..a0252be --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PlaceSnippets.kt @@ -0,0 +1,40 @@ +/* + * 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.snippets.kotlin.snippets + +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.OnMap3DClickListener +import com.google.android.gms.maps3d.model.LatLngAltitude + +class PlaceSnippets(private val map: GoogleMap3D) { + + + // [START maps_android_3d_place_click_kt] + /** + * Listens for clicks on 3D Places (buildings, POIs). + */ + fun listenToPlaceClicks() { + map.setMap3DClickListener(object : OnMap3DClickListener { + override fun onMap3DClick(location: LatLngAltitude, placeId: String?) { + if (placeId != null) { + // Handle place click + } + } + }) + } + // [END maps_android_3d_place_click_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PolygonSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PolygonSnippets.kt new file mode 100644 index 0000000..2bcdf7a --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PolygonSnippets.kt @@ -0,0 +1,76 @@ +/* + * 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.snippets.kotlin.snippets + +import android.graphics.Color +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.model.AltitudeMode +import com.google.android.gms.maps3d.model.latLngAltitude +import com.google.android.gms.maps3d.model.polygonOptions + +class PolygonSnippets(private val map: GoogleMap3D) { + + // [START maps_android_3d_polygon_add_kt] + /** + * Adds a simple polygon to the map. + */ + fun addBasicPolygon() { + val points = listOf( + latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 0.0 }, + latLngAltitude { latitude = 37.42; longitude = -122.09; altitude = 0.0 }, + latLngAltitude { latitude = 37.43; longitude = -122.09; altitude = 0.0 }, + latLngAltitude { latitude = 37.43; longitude = -122.08; altitude = 0.0 }, + latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 0.0 } + ) + + val options = polygonOptions { + path = points + fillColor = Color.RED + strokeColor = Color.BLUE + strokeWidth = 5.0 + altitudeMode = AltitudeMode.CLAMP_TO_GROUND + } + + val polygon = map.addPolygon(options) + } + // [END maps_android_3d_polygon_add_kt] + + // [START maps_android_3d_polygon_extruded_kt] + /** + * Adds an extruded polygon with transparency. + */ + fun addExtrudedPolygon() { + val points = listOf( + latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 50.0 }, + latLngAltitude { latitude = 37.42; longitude = -122.09; altitude = 50.0 }, + latLngAltitude { latitude = 37.43; longitude = -122.09; altitude = 50.0 }, + latLngAltitude { latitude = 37.43; longitude = -122.08; altitude = 50.0 }, + latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 50.0 } + ) + + val options = polygonOptions { + path = points + fillColor = 0x88FF0000.toInt() // Semi-transparent red + extruded = true + geodesic = true + altitudeMode = AltitudeMode.RELATIVE_TO_GROUND + } + + val polygon = map.addPolygon(options) + } + // [END maps_android_3d_polygon_extruded_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PolylineSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PolylineSnippets.kt new file mode 100644 index 0000000..dc4295e --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PolylineSnippets.kt @@ -0,0 +1,74 @@ +/* + * 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.snippets.kotlin.snippets + +import android.graphics.Color +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.model.AltitudeMode +import com.google.android.gms.maps3d.model.latLngAltitude +import com.google.android.gms.maps3d.model.polylineOptions + +class PolylineSnippets(private val map: GoogleMap3D) { + + // [START maps_android_3d_polyline_add_kt] + /** + * Adds a basic polyline to the map. + */ + fun addBasicPolyline() { + val points = listOf( + latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 0.0 }, + latLngAltitude { latitude = 37.43; longitude = -122.09; altitude = 0.0 }, + latLngAltitude { latitude = 37.44; longitude = -122.08; altitude = 0.0 } + ) + + val options = polylineOptions { + path = points + strokeColor = Color.RED + strokeWidth = 10.0 + altitudeMode = AltitudeMode.CLAMP_TO_GROUND + } + + val polyline = map.addPolyline(options) + } + // [END maps_android_3d_polyline_add_kt] + + // [START maps_android_3d_polyline_options_kt] + /** + * Adds a styled polyline with complex configuration. + */ + fun addStyledPolyline() { + val points = listOf( + latLngAltitude { latitude = 37.42; longitude = -122.08; altitude = 50.0 }, + latLngAltitude { latitude = 37.43; longitude = -122.09; altitude = 100.0 } + ) + + val options = polylineOptions { + path = points + strokeColor = 0xFFFF00FF.toInt() // Magenta + strokeWidth = 20.0 + outerColor = 0xFF00FF00.toInt() // Green + outerWidth = 2.0 + altitudeMode = AltitudeMode.RELATIVE_TO_GROUND + extruded = true + geodesic = true + drawsOccludedSegments = true + } + + val polyline = map.addPolyline(options) + } + // [END maps_android_3d_polyline_options_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PopoverSnippets.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PopoverSnippets.kt new file mode 100644 index 0000000..035777a --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/snippets/PopoverSnippets.kt @@ -0,0 +1,82 @@ +/* + * 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.snippets.kotlin.snippets + +import android.content.Context +import android.widget.TextView +import com.google.android.gms.maps3d.GoogleMap3D +import com.google.android.gms.maps3d.model.AltitudeMode +import com.google.android.gms.maps3d.model.latLngAltitude +import com.google.android.gms.maps3d.model.markerOptions +import com.example.snippets.common.R +import android.graphics.Color +import com.google.android.gms.maps3d.model.popoverOptions + +class PopoverSnippets(private val context: Context, private val map: GoogleMap3D) { + + // [START maps_android_3d_popover_add_kt] + /** + * Adds a popover anchored to a marker. + */ + fun addPopoverToMarker() { + // Create a marker first + val markerOptions = markerOptions { + position = latLngAltitude { latitude = 37.422; longitude = -122.084; altitude = 0.0 } + } + val marker = map.addMarker(markerOptions) ?: return + + // Create a custom view for the popover + val textView = TextView(context).apply { + text = context.getString(R.string.popover_hello) + setPadding(16, 16, 16, 16) + setBackgroundColor(Color.WHITE) + } + + // Add popover anchored to the marker + val options = popoverOptions { + content = textView + positionAnchor = marker + altitudeMode = AltitudeMode.RELATIVE_TO_GROUND + } + + val popover = map.addPopover(options) + + // You can show/hide it + popover.show() + } + // [END maps_android_3d_popover_add_kt] + + // [START maps_android_3d_popover_options_kt] + /** + * Adds a configured popover (auto-close enabled, auto-pan disabled). + */ + fun addConfiguredPopover() { + val textView = TextView(context).apply { + text = context.getString(com.example.snippets.common.R.string.popover_info) + } + + val options = popoverOptions { + content = textView + positionAnchor = latLngAltitude { latitude = 0.0; longitude = 0.0; altitude = 0.0 } + autoCloseEnabled = true // Close when clicking elsewhere + autoPanEnabled = false // Do not pan to popover + } + + map.addPopover(options) + } + // [END maps_android_3d_popover_options_kt] +} diff --git a/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/ui/theme/Theme.kt b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/ui/theme/Theme.kt new file mode 100644 index 0000000..0420e9a --- /dev/null +++ b/snippets/kotlin-app/src/main/java/com/example/snippets/kotlin/ui/theme/Theme.kt @@ -0,0 +1,75 @@ +/* + * 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.snippets.kotlin.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = androidx.compose.ui.graphics.Color(0xFFD0BCFF), + secondary = androidx.compose.ui.graphics.Color(0xFFCCC2DC), + tertiary = androidx.compose.ui.graphics.Color(0xFFEFB8C8) +) + +private val LightColorScheme = lightColorScheme( + primary = androidx.compose.ui.graphics.Color(0xFF6650a4), + secondary = androidx.compose.ui.graphics.Color(0xFF625b71), + tertiary = androidx.compose.ui.graphics.Color(0xFF7D5260) +) + +@Composable +fun SnippetsTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = androidx.compose.material3.Typography(), + content = content + ) +} diff --git a/snippets/kotlin-app/src/main/res/drawable/ic_launcher_background.xml b/snippets/kotlin-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..7ba6a0f --- /dev/null +++ b/snippets/kotlin-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/snippets/kotlin-app/src/main/res/drawable/ic_launcher_foreground.xml b/snippets/kotlin-app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..7451489 --- /dev/null +++ b/snippets/kotlin-app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/snippets/kotlin-app/src/main/res/mipmap-anydpi/ic_launcher.xml b/snippets/kotlin-app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..fee4243 --- /dev/null +++ b/snippets/kotlin-app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/snippets/kotlin-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/snippets/kotlin-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..fee4243 --- /dev/null +++ b/snippets/kotlin-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/snippets/kotlin-app/src/main/res/values/colors.xml b/snippets/kotlin-app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c4e60c9 --- /dev/null +++ b/snippets/kotlin-app/src/main/res/values/colors.xml @@ -0,0 +1,26 @@ + + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + diff --git a/snippets/kotlin-app/src/main/res/values/themes.xml b/snippets/kotlin-app/src/main/res/values/themes.xml new file mode 100644 index 0000000..8e80fea --- /dev/null +++ b/snippets/kotlin-app/src/main/res/values/themes.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/snippets/local.defaults.properties b/snippets/local.defaults.properties new file mode 100644 index 0000000..48e6efe --- /dev/null +++ b/snippets/local.defaults.properties @@ -0,0 +1 @@ +MAPS3D_API_KEY=DEFAULT_API_KEY diff --git a/snippets/settings.gradle.kts b/snippets/settings.gradle.kts new file mode 100644 index 0000000..76fdd4a --- /dev/null +++ b/snippets/settings.gradle.kts @@ -0,0 +1,51 @@ +/* + * 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. + */ + +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + val useLocalMaven = providers.gradleProperty("use_local_maven") + .getOrElse("false") + .toBoolean() + + if (useLocalMaven) { + mavenLocal() + } + google() + mavenCentral() + } +} + +rootProject.name = "Snippets" +include(":kotlin-app") +include(":java-app") +include(":common")