From 203112a6611510d3669cff73e554c00985988805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20L=C3=B3pez-Ma=C3=B1as?= Date: Sat, 15 Aug 2026 01:38:52 +0700 Subject: [PATCH 01/17] build: add .editorconfig for consistent code formatting (#2388) Adds a root .editorconfig with basic formatting conventions (UTF-8, LF line endings, trailing whitespace trimming, final newline, and indentation rules for Kotlin, Java, Gradle, XML, YAML/JSON, and Markdown) so contributors get consistent defaults across editors and IDEs. Fixes #964 --- .editorconfig | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..ad4bc5444 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{kt,kts}] +indent_size = 4 +max_line_length = 100 +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_packages_to_use_import_on_demand = unset + +[*.java] +indent_size = 4 +max_line_length = 100 + +[*.{xml,gradle}] +indent_size = 4 + +[*.{yml,yaml,json}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.{bat,cmd}] +end_of_line = crlf + +[Makefile] +indent_style = tab From 748a128b6d5363c470cb9d5b86e49e32f9c61476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20L=C3=B3pez-Ma=C3=B1as?= Date: Tue, 18 Aug 2026 03:53:43 +0700 Subject: [PATCH 02/17] build: update dependencies to latest versions (#2387) * build: update dependencies to latest versions Bumps the version catalog and a handful of hardcoded snippet dependencies to their latest releases, including major upgrades: AGP 8.13.2 -> 9.3.1, Gradle 9.1.0 -> 9.7.0, Kotlin 2.2.0 -> 2.4.10, KSP 2.2.20-2.0.4 -> 2.3.11 (now versioned independently of Kotlin), Maps Compose 7.0.0/6.12.0 -> 8.4.0, Places(-ktx) and android-maps-utils to their latest majors. Also aligns app-rx's hardcoded play-services-maps/places versions with the rest of the repo. * build: bump actions/checkout to v7 and actions/setup-python to v7 Both are new majors since the last actions bump (#2377). Verified no workflow uses pull_request_target/workflow_run (affected by checkout v7's fork-checkout change) or setup-python's removed pip-install input, so no other changes are needed. * docs: sync WearOS install-snippet version comment with catalog scripts/update_docs_versions.py enforces this in CI; the wear bump to 1.4.0 left the inline comment stale. * build: regenerate Gradle wrapper for 9.7.0 via official wrapper task Ran ./gradlew wrapper --gradle-version=9.7.0 instead of hand-editing gradle-wrapper.properties, since that also refreshes gradle-wrapper.jar and the gradlew/gradlew.bat scripts (and picks up new 9.x wrapper properties like retries/retryBackOffMs). Drops the distributionSha256Sum pin as requested. * build: migrate to AGP 9 built-in Kotlin support AGP 9 provides Kotlin support natively and refuses to configure a module that still applies org.jetbrains.kotlin.android alongside it. Removes that plugin (all its declaration styles: catalog alias, kotlin("android"), and hardcoded id(...)) from every module actually wired into settings.gradle.kts, and drops the now-unused kotlin-android catalog entry. Also fixes two things AGP 9 surfaced as hard errors during configuration, unrelated to the plugin removal itself: - ApiDemos:java-app had a vestigial buildFeatures.compose = true with no Kotlin sources or Compose dependencies; AGP 9 now requires the Compose Compiler plugin whenever compose is enabled, so the dead flag is removed instead. - tutorials/kotlin/Polygons used the now-unsupported proguard-android.txt default file, replaced with proguard-android-optimize.txt. Standalone tutorial/sample directories not wired into settings.gradle.kts (app-rx, the java/ and duplicate kotlin/ tutorial folders) are untouched since they aren't part of the actual build graph or CI. * build: bump compileSdk/targetSdk to 37 androidx.core:core-ktx 1.19.0 requires compiling against API 37+; CI's checkDebugAarMetadata caught this. Also switches app-utils-ktx off its hardcoded compileSdk/targetSdk = 36 onto the catalog value, matching every other module. * build: pin GitHub Actions to commit SHAs The org's zizmor policy check (triggered whenever workflow files change) requires every `uses:` reference to be pinned to a full commit SHA rather than a floating version tag. Touching these three files for the checkout/setup-python bump made the check actually run for the first time in a while and it flagged all 36 `uses:` lines in them (not just the ones changed), so pins every action reference across build.yml, lint.yml, and generate-v3.yml, each with a version comment. release-please.yml is untouched by this PR and wasn't flagged, so left as-is. * build: add explicit workflow permissions and disable credential persistence zizmor flagged two more findings once these files started being scanned: excessive-permissions (Medium, blocking) because none of the three workflows declared an explicit permissions: block and so ran with the default broad GITHUB_TOKEN scope, and artipacked (Low, informational) because checkout steps left credentials persisted in the git config, which get exposed to anything that later reads the workspace (e.g. artifact uploads). - build.yml: contents: read (checkout + build only, no writes) - lint.yml: contents: read, security-events: write (needed for github/codeql-action/upload-sarif) - generate-v3.yml: contents: read (the actual PR push uses its own SYNCED_GITHUB_TOKEN_REPO secret via peter-evans/create-pull-request, not the default token) - persist-credentials: false added to every actions/checkout step across all three files; none of them do a subsequent git push with the default token, so this is safe. * fix: findViewById instead of View? for applyInsets calls applyInsets(container: View) takes a non-null View, but every kotlin-app activity called it as applyInsets(findViewById(R.id.map_container)). Kotlin's newer compiler no longer treats an explicit nullable type argument on a platform-typed Java generic method leniently, so this now hard fails to compile instead of silently working. All 24 call sites always assumed a non-null result anyway (no null check), so this just drops the incorrect nullable type argument. * fix: adapt app-utils-ktx snippets to android-maps-utils 5.1.1 API The android-maps-utils bump (3.19.0 -> 5.1.1, part of the dependency update) rewrote the library in Kotlin, which is a source-incompatible change for these snippets: - ClusterItem is now a Kotlin interface with val properties (position, title, snippet, zIndex: Float?), not Java-style getter methods. Clustering.kt and Multilayer.kt's MyItem implementations used `override fun getPosition()` etc., which no longer overrides anything Kotlin recognizes; converted to `override val` properties matching the new interface, constructor-promoted where possible. - KmlLayer/KmlContainer expose getContainers()/getPlacemarks() as plain functions, not Kotlin properties, and KmlContainer's backing fields are now private. Feature.getId() is likewise a function. KML.kt used property-access syntax (layer.containers, feature.id) for all of these; switched to explicit method calls. - GeoJsonLayer's default style accessor is getDefaultPointStyle(), not a defaultPointStyle property; fixed in GeoJSON.kt. - KmlLayer/GeoJsonLayer constructors now take non-null Context/InputStream/JSONObject instead of the old nullable platform types. These snippet files use throwaway `= null` placeholders for doc purposes (never actually run), so added `!!` at each call site to match, consistent with the existing `map!!` pattern already used nearby. Verified each API shape against the actual v5.1.1 source on googlemaps/android-maps-utils (the library moved to a Kotlin rewrite there) rather than guessing. * fix: remaining android-maps-utils 5.1.1 API breaks Two more spots the compiler hadn't reached yet in the previous fix pass: - GeoJsonPointStyle (Kotlin, android-maps-utils 5.1.1) exposes isDraggable()/setDraggable(), getTitle()/setTitle(), getSnippet()/setSnippet() as plain functions, not var properties, so app-utils-ktx/GeoJSON.kt's `pointStyle.isDraggable = true` style assignments don't compile; switched to explicit setter calls. - KmlContainer.getProperty() now returns String? (nullable); KML.kt passed it straight to Log.i's non-null second parameter, added !! (guarded by the existing hasProperty() check just above it). - The JSONObject-argument GeoJsonLayer constructor is now annotated @Throws(JSONException::class) in Kotlin, which surfaces in Java as a checked exception. snippets/app-utils (Java)'s addGeoJsonLayerJsonObject() didn't declare it; added `throws JSONException` to match its sibling method's existing pattern. * fix: remember MarkerState in FireMarkers to satisfy lint AGP 9's bundled lint now flags UnrememberedMutableState as an error (previously warning/unreported): MarkerState(...) was created fresh every recomposition, which would also reset marker drag state. Wraps it in remember(markerData.id) so each marker's state survives recomposition and is invalidated by React iOnly when the marker's identity actually changes. * refactor(snippets): use version catalog in documentation snippets and update Places SDK - Update dependencies in snippets (app-compose, app-utils, app-places-ktx) to use the Gradle version catalog (libs.*), resolving UseTomlInstead lint findings flagged by code scanning. - Add documentation comments within region tags showing both the recommended version catalog configuration (TOML block) and the standalone coordinate declaration for non-catalog projects. - Replace deprecated places-ktx dependency with core Places SDK (libs.places), as Kotlin extensions and coroutines are built directly into the Places SDK. - Add mapsUtils to gradle/libs.versions.toml. - Add missing androidTest dependencies (ext.junit, espresso.idling.resource) to ApiDemos:kotlin-app and update LatLngSubject / LatLngBoundsSubject custom Truth subject factories for Kotlin 2.4 / Truth 1.4.5 compatibility. - Add .kotlin/ compiler directory to .gitignore. --------- Co-authored-by: Dale Hawkins <107309+dkhawk@users.noreply.github.com> --- .github/workflows/build.yml | 49 +++++++++------ .github/workflows/generate-v3.yml | 13 ++-- .github/workflows/lint.yml | 36 ++++++----- .gitignore | 3 +- ApiDemos/project/common-ui/build.gradle.kts | 1 - ApiDemos/project/java-app/build.gradle.kts | 1 - ApiDemos/project/kotlin-app/build.gradle.kts | 3 +- .../kotlindemos/truth/LatLngBoundsSubject.kt | 12 +++- .../kotlindemos/truth/LatLngSubject.kt | 16 ++++- ...ackgroundColorCustomizationDemoActivity.kt | 2 +- ...orCustomizationProgrammaticDemoActivity.kt | 2 +- .../CloudBasedMapStylingDemoActivity.kt | 2 +- .../DataDrivenBoundariesActivity.kt | 2 +- .../DataDrivenDatasetStylingActivity.kt | 2 +- .../example/kotlindemos/EventsDemoActivity.kt | 2 +- .../kotlindemos/LiteListDemoActivity.kt | 2 +- .../kotlindemos/LocationSourceDemoActivity.kt | 2 +- .../kotlindemos/MapColorSchemeActivity.kt | 2 +- .../kotlindemos/MapInPagerDemoActivity.kt | 2 +- ...arkerCloseInfoWindowOnRetapDemoActivity.kt | 2 +- .../kotlindemos/MultiMapDemoActivity.kt | 2 +- .../kotlindemos/MyLocationDemoActivity.kt | 2 +- .../kotlindemos/PolygonDemoActivity.kt | 2 +- .../kotlindemos/PolylineDemoActivity.kt | 2 +- .../kotlindemos/ProgrammaticDemoActivity.kt | 2 +- .../kotlindemos/RetainMapDemoActivity.kt | 2 +- ...litStreetViewPanoramaAndMapDemoActivity.kt | 2 +- .../StreetViewPanoramaBasicDemoActivity.kt | 2 +- .../StreetViewPanoramaEventsDemoActivity.kt | 2 +- .../StreetViewPanoramaViewDemoActivity.kt | 2 +- .../kotlindemos/StyledMapDemoActivity.kt | 2 +- .../example/kotlindemos/TagsDemoActivity.kt | 2 +- FireMarkers/app/build.gradle.kts | 1 - .../com/example/firemarkers/MainActivity.kt | 4 +- FireMarkers/build.gradle.kts | 1 - WearOS/Wearable/build.gradle.kts | 3 +- WearOS/build.gradle.kts | 1 - build.gradle.kts | 1 - gradle/libs.versions.toml | 57 +++++++++--------- gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 47505 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 6 +- gradlew.bat | 35 ++++------- snippets/app-compose/build.gradle.kts | 16 ++++- snippets/app-ktx/build.gradle.kts | 1 - snippets/app-places-ktx/build.gradle.kts | 18 +++++- snippets/app-rx/build.gradle.kts | 4 +- snippets/app-utils-ktx/build.gradle.kts | 5 +- .../com/example/app_utils_ktx/Clustering.kt | 32 ++-------- .../java/com/example/app_utils_ktx/GeoJSON.kt | 10 +-- .../java/com/example/app_utils_ktx/KML.kt | 14 ++--- .../com/example/app_utils_ktx/Multilayer.kt | 39 +++--------- snippets/app-utils/build.gradle.kts | 16 ++++- .../java/com/example/app_utils/GeoJSON.java | 2 +- snippets/app/build.gradle.kts | 1 - snippets/build.gradle.kts | 1 - .../kotlin/Polygons/app/build.gradle.kts | 3 +- 57 files changed, 235 insertions(+), 221 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ae5dc370..0f7fe1095 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,9 @@ on: types: [ build ] workflow_dispatch: +permissions: + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: build-ApiDemos: @@ -32,16 +35,18 @@ jobs: timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up Java 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build and check run: | @@ -55,16 +60,18 @@ jobs: timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up Java 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build and check run: ./gradlew :WearOS:Wearable:assembleDebug @@ -74,16 +81,18 @@ jobs: timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up Java 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build and check run: ./gradlew :FireMarkers:app:assembleDebug @@ -93,16 +102,18 @@ jobs: timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up Java 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build and check run: | @@ -118,16 +129,18 @@ jobs: timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up Java 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build and check run: | @@ -143,9 +156,11 @@ jobs: - build-Snippets - build-tutorials steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up Java 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' diff --git a/.github/workflows/generate-v3.yml b/.github/workflows/generate-v3.yml index 3a25059db..86f0f0ac3 100644 --- a/.github/workflows/generate-v3.yml +++ b/.github/workflows/generate-v3.yml @@ -19,22 +19,27 @@ on: repository_dispatch: types: [ generate-v3 ] +permissions: + contents: read + jobs: generate-v3: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set up JDK 17 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: 17 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Install NDK run: | @@ -48,7 +53,7 @@ jobs: echo "files-changed=$(git status -s | wc -l)" >> $GITHUB_OUTPUT - name: PR Changes - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 if: steps.gradlew-generate-v3.outputs.files-changed > 0 with: token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2fc362379..930a11e7c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,22 +19,28 @@ on: branches: - main +permissions: + contents: read + security-events: write + jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up JDK 17 - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'temurin' java-version: '21' - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.x' @@ -42,7 +48,7 @@ jobs: run: python3 scripts/update_docs_versions.py --check - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Run Android Lint run: | @@ -59,67 +65,67 @@ jobs: ./gradlew :FireMarkers:app:lintDebug - name: Upload SARIF for ApiDemos:kotlin-app - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: ApiDemos/project/kotlin-app/build/reports/lint-results-debug.sarif category: ApiDemos-kotlin-app - name: Upload SARIF for ApiDemos:java-app - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: ApiDemos/project/java-app/build/reports/lint-results-debug.sarif category: ApiDemos-java-app - name: Upload SARIF for ApiDemos:common-ui - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: ApiDemos/project/common-ui/build/reports/lint-results-debug.sarif category: ApiDemos-common-ui - name: Upload SARIF for snippets:app - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: snippets/app/build/reports/lint-results-debug.sarif category: snippets-app - name: Upload SARIF for snippets:app-utils - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: snippets/app-utils/build/reports/lint-results-debug.sarif category: snippets-app-utils - name: Upload SARIF for snippets:app-utils-ktx - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: snippets/app-utils-ktx/build/reports/lint-results-debug.sarif category: snippets-app-utils-ktx - name: Upload SARIF for snippets:app-places-ktx - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: snippets/app-places-ktx/build/reports/lint-results-debug.sarif category: snippets-app-places-ktx - name: Upload SARIF for snippets:app-ktx - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: snippets/app-ktx/build/reports/lint-results-debug.sarif category: snippets-app-ktx - name: Upload SARIF for snippets:app-compose - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: snippets/app-compose/build/reports/lint-results-debug.sarif category: snippets-app-compose - name: Upload SARIF for WearOS:Wearable - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: WearOS/Wearable/build/reports/lint-results-debug.sarif category: WearOS-Wearable - name: Upload SARIF for FireMarkers:app - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: FireMarkers/app/build/reports/lint-results-debug.sarif category: FireMarkers-app \ No newline at end of file diff --git a/.gitignore b/.gitignore index 275449d61..80934aa5c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ secrets.properties # This covers new IDEs, like Antigravity .vscode/ -**/bin/ \ No newline at end of file +**/bin/ +.kotlin/ \ No newline at end of file diff --git a/ApiDemos/project/common-ui/build.gradle.kts b/ApiDemos/project/common-ui/build.gradle.kts index dfcd8352b..595680348 100644 --- a/ApiDemos/project/common-ui/build.gradle.kts +++ b/ApiDemos/project/common-ui/build.gradle.kts @@ -18,7 +18,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) } android { diff --git a/ApiDemos/project/java-app/build.gradle.kts b/ApiDemos/project/java-app/build.gradle.kts index 6d278e86f..73e208bf6 100644 --- a/ApiDemos/project/java-app/build.gradle.kts +++ b/ApiDemos/project/java-app/build.gradle.kts @@ -34,7 +34,6 @@ android { } buildFeatures { - compose = true buildConfig = true viewBinding = true } diff --git a/ApiDemos/project/kotlin-app/build.gradle.kts b/ApiDemos/project/kotlin-app/build.gradle.kts index 76cd6784e..810fa6e18 100644 --- a/ApiDemos/project/kotlin-app/build.gradle.kts +++ b/ApiDemos/project/kotlin-app/build.gradle.kts @@ -18,7 +18,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.secrets.gradle.plugin) } @@ -94,6 +93,8 @@ dependencies { // Tests testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.idling.resource) androidTestImplementation(libs.junit) androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.truth) diff --git a/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngBoundsSubject.kt b/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngBoundsSubject.kt index 2937bbc1b..4710bd1de 100644 --- a/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngBoundsSubject.kt +++ b/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngBoundsSubject.kt @@ -26,7 +26,7 @@ import com.google.common.truth.Truth */ class LatLngBoundsSubject private constructor( failureMetadata: FailureMetadata, - private val actual: LatLngBounds + private val actual: LatLngBounds? ) : Subject(failureMetadata, actual) { /** @@ -34,6 +34,10 @@ class LatLngBoundsSubject private constructor( * a small tolerance to account for floating-point inaccuracies. */ fun containsWithTolerance(expected: LatLng) { + if (actual == null) { + failWithActual("expected to contain", expected) + return + } val tolerance = 1e-5 val contains = actual.southwest.latitude - tolerance <= expected.latitude && expected.latitude <= actual.northeast.latitude + tolerance && @@ -60,11 +64,13 @@ class LatLngBoundsSubject private constructor( } companion object { + fun latLngBounds(): Factory = Factory(::LatLngBoundsSubject) + /** * Creates a [LatLngBoundsSubject] for asserting on the given [LatLngBounds]. */ - fun assertThat(actual: LatLngBounds): LatLngBoundsSubject { - return Truth.assertAbout(::LatLngBoundsSubject).that(actual) + fun assertThat(actual: LatLngBounds?): LatLngBoundsSubject { + return Truth.assertAbout(latLngBounds()).that(actual) } } } diff --git a/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngSubject.kt b/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngSubject.kt index 73ed69ad0..9cdcdf06a 100644 --- a/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngSubject.kt +++ b/ApiDemos/project/kotlin-app/src/androidTest/java/com/example/kotlindemos/truth/LatLngSubject.kt @@ -32,7 +32,7 @@ import kotlin.math.abs */ class LatLngSubject private constructor( failureMetadata: FailureMetadata, - private val actual: LatLng + private val actual: LatLng? ) : Subject(failureMetadata, actual) { /** @@ -40,6 +40,10 @@ class LatLngSubject private constructor( * This is useful for accounting for floating-point inaccuracies. */ fun isNear(expected: LatLng) { + if (actual == null) { + failWithActual("expected to be near", expected) + return + } val tolerance = 1e-6 if (abs(actual.latitude - expected.latitude) > tolerance || abs(actual.longitude - expected.longitude) > tolerance @@ -63,6 +67,10 @@ class LatLngSubject private constructor( * Asserts that the actual [LatLng] is within [tolerance] meters of the [expected] [LatLng]. */ fun of(expected: LatLng) { + if (actual == null) { + failWithActual("expected to be within $tolerance meters of $expected", expected) + return + } val distance = SphericalUtil.computeDistanceBetween(actual, expected) if (distance > tolerance) { failWithActual("expected to be within $tolerance meters of $expected [was $distance meters away]", expected) @@ -71,11 +79,13 @@ class LatLngSubject private constructor( } companion object { + fun latLngs(): Factory = Factory(::LatLngSubject) + /** * Creates a [LatLngSubject] for asserting on the given [LatLng]. */ - fun assertThat(actual: LatLng): LatLngSubject { - return Truth.assertAbout(::LatLngSubject).that(actual) + fun assertThat(actual: LatLng?): LatLngSubject { + return Truth.assertAbout(latLngs()).that(actual) } } } diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationDemoActivity.kt index fad3716b4..417051e6e 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationDemoActivity.kt @@ -33,7 +33,7 @@ class BackgroundColorCustomizationDemoActivity : SamplesBaseActivity(), OnMapRea setContentView(R.layout.background_color_customization_demo) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? mapFragment?.getMapAsync(this) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } /** diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationProgrammaticDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationProgrammaticDemoActivity.kt index ed9070a96..11d39d4da 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationProgrammaticDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/BackgroundColorCustomizationProgrammaticDemoActivity.kt @@ -58,7 +58,7 @@ class BackgroundColorCustomizationProgrammaticDemoActivity : SamplesBaseActivity } else { mapFragment.getMapAsync(this) } - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(map: GoogleMap) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CloudBasedMapStylingDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CloudBasedMapStylingDemoActivity.kt index db5b5da5e..3622e4cd3 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CloudBasedMapStylingDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CloudBasedMapStylingDemoActivity.kt @@ -50,7 +50,7 @@ class CloudBasedMapStylingDemoActivity : SamplesBaseActivity(), OnMapReadyCallba val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? mapFragment!!.getMapAsync(this) setUpButtonListeners() - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(map: GoogleMap) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenBoundariesActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenBoundariesActivity.kt index 63ffac14c..26d0c7d82 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenBoundariesActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenBoundariesActivity.kt @@ -107,7 +107,7 @@ class DataDrivenBoundariesActivity : SamplesBaseActivity(), OnMapReadyCallback, setupBoundarySelectorButton() // Setup the new selector button // --- Insets --- - applyInsets(findViewById(R.id.map_container)) // Apply insets if needed + applyInsets(findViewById(R.id.map_container)) // Apply insets if needed } private fun setupBoundarySelectorButton() { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenDatasetStylingActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenDatasetStylingActivity.kt index 56019000a..62656c2f4 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenDatasetStylingActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/DataDrivenDatasetStylingActivity.kt @@ -124,7 +124,7 @@ class DataDrivenDatasetStylingActivity : SamplesBaseActivity(), OnMapReadyCallba buttonLayout = findViewById(com.example.common_ui.R.id.button_kyoto).parent as LinearLayout handleCutout() - applyInsets(findViewById(com.example.common_ui.R.id.map_container)) + applyInsets(findViewById(com.example.common_ui.R.id.map_container)) } private fun handleCutout() { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/EventsDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/EventsDemoActivity.kt index 46c42d5bb..384eba27a 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/EventsDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/EventsDemoActivity.kt @@ -42,7 +42,7 @@ class EventsDemoActivity : SamplesBaseActivity(), OnMapClickListener, cameraTextView = findViewById(R.id.camera_text) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? mapFragment?.getMapAsync(this) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(googleMap: GoogleMap) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LiteListDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LiteListDemoActivity.kt index 59c221125..717b709b0 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LiteListDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LiteListDemoActivity.kt @@ -78,7 +78,7 @@ class LiteListDemoActivity : SamplesBaseActivity() { adapter = mapAdapter setRecyclerListener(recycleListener) } - applyInsets(findViewById(com.example.common_ui.R.id.map_container)) + applyInsets(findViewById(com.example.common_ui.R.id.map_container)) } /** Create options menu to switch between the linear and grid layout managers. */ diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LocationSourceDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LocationSourceDemoActivity.kt index 358cd1e47..21fad59e8 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LocationSourceDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/LocationSourceDemoActivity.kt @@ -50,7 +50,7 @@ class LocationSourceDemoActivity : SamplesBaseActivity() { init(map = map) } lifecycle.addObserver(locationSource) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } @SuppressLint("MissingPermission") diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapColorSchemeActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapColorSchemeActivity.kt index b2c79d1b4..a2866b03e 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapColorSchemeActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapColorSchemeActivity.kt @@ -46,7 +46,7 @@ class MapColorSchemeActivity : buttonLight = findViewById(R.id.map_color_light_mode) buttonDark = findViewById(R.id.map_color_dark_mode) buttonFollowSystem = findViewById(R.id.map_color_follow_system_mode) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(googleMap: GoogleMap) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapInPagerDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapInPagerDemoActivity.kt index c50221d78..eff3890bf 100755 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapInPagerDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MapInPagerDemoActivity.kt @@ -42,7 +42,7 @@ class MapInPagerDemoActivity : SamplesBaseActivity() { // This is required to avoid a black flash when the map is loaded. The flash is due // to the use of a SurfaceView as the underlying view of the map. pager.requestTransparentRegion(pager) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } /** A simple fragment that displays a TextView. */ diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MarkerCloseInfoWindowOnRetapDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MarkerCloseInfoWindowOnRetapDemoActivity.kt index 62703f748..c9397e4e0 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MarkerCloseInfoWindowOnRetapDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MarkerCloseInfoWindowOnRetapDemoActivity.kt @@ -70,7 +70,7 @@ class MarkerCloseInfoWindowOnRetapDemoActivity : val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment OnMapAndViewReadyListener(mapFragment, this) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(googleMap: GoogleMap?) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MultiMapDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MultiMapDemoActivity.kt index 13c6a02fc..1ca406943 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MultiMapDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MultiMapDemoActivity.kt @@ -25,6 +25,6 @@ class MultiMapDemoActivity : SamplesBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.multimap_demo) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } } \ No newline at end of file diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MyLocationDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MyLocationDemoActivity.kt index 645996905..e86d74bff 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MyLocationDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MyLocationDemoActivity.kt @@ -56,7 +56,7 @@ class MyLocationDemoActivity : SamplesBaseActivity(), val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? mapFragment?.getMapAsync(this) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(googleMap: GoogleMap) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt index 5d3bca2ca..fea47f8b7 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt @@ -131,7 +131,7 @@ class PolygonDemoActivity : val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } // [START_EXCLUDE silent] diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt index 481dcb366..a871551ea 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt @@ -155,7 +155,7 @@ class PolylineDemoActivity : val mapFragment = supportFragmentManager.findFragmentById(com.example.common_ui.R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) - applyInsets(findViewById(com.example.common_ui.R.id.map_container)) + applyInsets(findViewById(com.example.common_ui.R.id.map_container)) } // [START_EXCLUDE silent] diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/ProgrammaticDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/ProgrammaticDemoActivity.kt index ae65dd0e7..b54d13098 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/ProgrammaticDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/ProgrammaticDemoActivity.kt @@ -47,7 +47,7 @@ class ProgrammaticDemoActivity : SamplesBaseActivity() { title("Marker") } } - applyInsets(findViewById(com.example.common_ui.R.id.map_container)) + applyInsets(findViewById(com.example.common_ui.R.id.map_container)) } companion object { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/RetainMapDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/RetainMapDemoActivity.kt index 7c0f3195f..36c22497f 100755 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/RetainMapDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/RetainMapDemoActivity.kt @@ -43,6 +43,6 @@ class RetainMapDemoActivity : SamplesBaseActivity() { title("Marker") } } - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } } \ No newline at end of file diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SplitStreetViewPanoramaAndMapDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SplitStreetViewPanoramaAndMapDemoActivity.kt index 88c16b5e5..bc653c8d7 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SplitStreetViewPanoramaAndMapDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SplitStreetViewPanoramaAndMapDemoActivity.kt @@ -60,7 +60,7 @@ class SplitStreetViewPanoramaAndMapDemoActivity : SamplesBaseActivity(), .draggable(true) ) } - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaBasicDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaBasicDemoActivity.kt index b3e8cf66d..40ea537b1 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaBasicDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaBasicDemoActivity.kt @@ -36,7 +36,7 @@ class StreetViewPanoramaBasicDemoActivity : SamplesBaseActivity() { // loaded which is when the savedInstanceState is null). savedInstanceState ?: panorama.setPosition(SYDNEY) } - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } companion object { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaEventsDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaEventsDemoActivity.kt index 8b6a14c9f..1930d9fa7 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaEventsDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaEventsDemoActivity.kt @@ -74,7 +74,7 @@ class StreetViewPanoramaEventsDemoActivity : SamplesBaseActivity(), // loaded which is when the savedInstanceState is null). savedInstanceState ?: streetViewPanorama.setPosition(SYDNEY) } - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onStreetViewPanoramaChange(location: StreetViewPanoramaLocation) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaViewDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaViewDemoActivity.kt index ef05f7f04..296844220 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaViewDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StreetViewPanoramaViewDemoActivity.kt @@ -45,7 +45,7 @@ class StreetViewPanoramaViewDemoActivity : SamplesBaseActivity() { // StreetViewPanoramaView requires that the Bundle you pass contain _ONLY_ // StreetViewPanoramaView SDK objects or sub-Bundles. streetViewPanoramaView.onCreate(savedInstanceState?.getBundle(STREETVIEW_BUNDLE_KEY)) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onResume() { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StyledMapDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StyledMapDemoActivity.kt index 49cb6c5e7..7df53b8fc 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StyledMapDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/StyledMapDemoActivity.kt @@ -62,7 +62,7 @@ class StyledMapDemoActivity : SamplesBaseActivity(), OnMapReadyCallback { val mapFragment = supportFragmentManager.findFragmentById(com.example.common_ui.R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) - applyInsets(findViewById(com.example.common_ui.R.id.map_container)) + applyInsets(findViewById(com.example.common_ui.R.id.map_container)) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt index 428915b94..d282bc03f 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt @@ -88,7 +88,7 @@ class TagsDemoActivity : SamplesBaseActivity(), val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment OnMapAndViewReadyListener(mapFragment, this) - applyInsets(findViewById(R.id.map_container)) + applyInsets(findViewById(R.id.map_container)) } override fun onMapReady(googleMap: GoogleMap?) { diff --git a/FireMarkers/app/build.gradle.kts b/FireMarkers/app/build.gradle.kts index eb56c8aff..d54da0788 100644 --- a/FireMarkers/app/build.gradle.kts +++ b/FireMarkers/app/build.gradle.kts @@ -22,7 +22,6 @@ plugins { // Compose. They handle the core compilation, packaging, and resource processing. // --------------------------------------------------------------------------------------------- alias(libs.plugins.android.application) // Applies the core Android application plugin. - alias(libs.plugins.kotlin.android) // Enables Kotlin language support for Android. alias(libs.plugins.kotlin.compose) // Provides support for the Jetpack Compose compiler. // --------------------------------------------------------------------------------------------- diff --git a/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt b/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt index f6d603f4c..a970ef38a 100644 --- a/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt +++ b/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt @@ -150,7 +150,9 @@ fun MapScreen(modifier: Modifier = Modifier) { ) { markers.forEach { markerData -> Marker( - state = MarkerState(position = LatLng(markerData.latitude, markerData.longitude)), + state = remember(markerData.id) { + MarkerState(position = LatLng(markerData.latitude, markerData.longitude)) + }, title = markerData.label, icon = BitmapDescriptorFactory.defaultMarker(markerData.color) ) diff --git a/FireMarkers/build.gradle.kts b/FireMarkers/build.gradle.kts index 6f9778da7..d3068fd2f 100644 --- a/FireMarkers/build.gradle.kts +++ b/FireMarkers/build.gradle.kts @@ -17,7 +17,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.hilt.android) apply false diff --git a/WearOS/Wearable/build.gradle.kts b/WearOS/Wearable/build.gradle.kts index d7670782a..7a6596fdb 100644 --- a/WearOS/Wearable/build.gradle.kts +++ b/WearOS/Wearable/build.gradle.kts @@ -17,7 +17,6 @@ plugins { id("com.android.application") id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") - kotlin("android") // The Kotlin plugin is applied differently in Kotlin DSL } android { @@ -63,7 +62,7 @@ dependencies { // // [versions] // playServicesMaps = "20.0.0" - // wear = "1.3.0" + // wear = "1.4.0" // wearable = "2.9.0" // // [libraries] diff --git a/WearOS/build.gradle.kts b/WearOS/build.gradle.kts index d2f8d297a..8dd08da3c 100644 --- a/WearOS/build.gradle.kts +++ b/WearOS/build.gradle.kts @@ -17,5 +17,4 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.secrets.gradle.plugin) apply false - alias(libs.plugins.kotlin.android) apply false } diff --git a/build.gradle.kts b/build.gradle.kts index 5959e3ce8..f6d78cff3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.secrets.gradle.plugin) apply false alias(libs.plugins.hilt.android) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c0f0ae9dd..189e0dc0c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,66 +1,67 @@ [versions] # Project Configuration -androidGradlePlugin = "8.13.2" +androidGradlePlugin = "9.3.1" minSdk = "24" -compileSdk = "36" -targetSdk = "36" +compileSdk = "37" +targetSdk = "37" versionCode = "1" versionName = "2.0.2" # {x-release-please-version} javaVersion = "17" # Kotlin & Coroutines -kotlin = "2.2.0" -kotlinxCoroutinesTest = "1.10.2" -kotlinxDatetime = "0.7.1" -ksp = "2.2.20-2.0.4" +kotlin = "2.4.10" +kotlinxCoroutinesTest = "1.11.0" +kotlinxDatetime = "0.8.0" +ksp = "2.3.11" # AndroidX Core & Jetpack -activity = "1.12.2" -activityKtx = "1.12.2" +activity = "1.13.0" +activityKtx = "1.13.0" androidxJunit = "1.3.0" # Test ext -appcompat = "1.7.1" +appcompat = "1.8.0" cardview = "1.0.0" -constraintlayout = "2.2.1" -coreKtx = "1.17.0" -lifecycle = "2.10.0" +constraintlayout = "2.2.2" +coreKtx = "1.19.0" +lifecycle = "2.11.0" multidex = "2.0.1" -navigation = "2.9.6" +navigation = "2.9.8" recyclerview = "1.4.0" # Jetpack Compose -compose = "1.10.1" -composeBom = "2026.01.00" -hiltNavigationCompose = "1.3.0" -material = "1.13.0" # View-based Material +compose = "1.12.0" +composeBom = "2026.08.00" +hiltNavigationCompose = "1.4.0" +material = "1.14.0" # View-based Material material3 = "1.4.0" materialIconsExtended = "1.7.8" # Google Maps & Places -mapsCompose = "7.0.0" -mapsKtx = "6.0.0" -places = "5.1.1" +mapsCompose = "8.4.0" +mapsKtx = "6.3.0" +mapsUtils = "5.1.1" +places = "5.3.0" playServicesMaps = "20.0.0" secretsGradlePlugin = "2.0.1" # Wear OS -wear = "1.3.0" +wear = "1.4.0" wearable = "2.9.0" # Dependency Injection -dagger = "2.57.2" -hilt = "2.57.2" +dagger = "2.60.1" +hilt = "2.60.1" # Testing espresso = "3.7.0" junit = "4.13.2" -mockito = "6.2.2" +mockito = "6.3.0" robolectric = "4.16.1" truth = "1.4.5" turbine = "1.2.1" -uiautomator = "2.3.0" +uiautomator = "2.4.0" # Firebase -firebaseBom = "34.8.0" +firebaseBom = "34.17.0" firebaseDatabase = "22.0.1" # Third Party @@ -102,6 +103,7 @@ ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", versio # Maps & Places maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" } maps-ktx = { group = "com.google.maps.android", name = "maps-ktx", version.ref = "mapsKtx" } +maps-utils = { module = "com.google.maps.android:android-maps-utils", version.ref = "mapsUtils" } maps-utils-ktx = { group = "com.google.maps.android", name = "maps-utils-ktx", version.ref = "mapsKtx" } places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" } @@ -143,7 +145,6 @@ volley = { group = "com.android.volley", name = "volley", version.ref = "volley" android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..eddabd2eef8d94a5437d6168ff9c87a78ff725b3 100644 GIT binary patch delta 39250 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%0D-`kFK_aWqG#Tpvd+Hgh; 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 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 +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,29 +65,18 @@ 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 +"%COMSPEC%" /c exit 1 :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" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/snippets/app-compose/build.gradle.kts b/snippets/app-compose/build.gradle.kts index fb7d3134b..129c8e19d 100644 --- a/snippets/app-compose/build.gradle.kts +++ b/snippets/app-compose/build.gradle.kts @@ -16,7 +16,6 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.secrets.gradle.plugin) } @@ -86,8 +85,21 @@ dependencies { implementation(libs.lifecycle.runtime.ktx) // [END_EXCLUDE] + // Modern Android projects use version catalogs to manage dependencies. To include the Maps Compose library, + // first add the following to your gradle/libs.versions.toml file: + // + // [versions] + // mapsCompose = "8.4.0" + // + // [libraries] + // maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" } + // // Android Maps Compose composables for the Maps SDK for Android - implementation("com.google.maps.android:maps-compose:6.12.0") + implementation(libs.maps.compose) + + // If your project does not use a version catalog, you can use the following dependency instead: + // + // implementation("com.google.maps.android:maps-compose:8.4.0") } // [END maps_android_compose_dependency] diff --git a/snippets/app-ktx/build.gradle.kts b/snippets/app-ktx/build.gradle.kts index 143020ada..e305a70b0 100644 --- a/snippets/app-ktx/build.gradle.kts +++ b/snippets/app-ktx/build.gradle.kts @@ -16,7 +16,6 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/app-places-ktx/build.gradle.kts b/snippets/app-places-ktx/build.gradle.kts index 321fce956..1076d3b21 100644 --- a/snippets/app-places-ktx/build.gradle.kts +++ b/snippets/app-places-ktx/build.gradle.kts @@ -16,7 +16,6 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } @@ -81,8 +80,21 @@ dependencies { androidTestImplementation(libs.espresso.core) // [END_EXCLUDE] - // KTX for the Places SDK for Android library - implementation("com.google.maps.android:places-ktx:3.5.0") + // Modern Android projects use version catalogs to manage dependencies. To include the Places SDK for Android, + // first add the following to your gradle/libs.versions.toml file: + // + // [versions] + // places = "5.3.0" + // + // [libraries] + // places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } + // + // Places SDK for Android (Kotlin extensions and coroutines are built directly into the Places SDK) + implementation(libs.places) + + // If your project does not use a version catalog, you can use the following dependency instead: + // + // implementation("com.google.android.libraries.places:places:5.3.0") } // [END places_android_ktx_install_snippet] diff --git a/snippets/app-rx/build.gradle.kts b/snippets/app-rx/build.gradle.kts index eecc05663..bb48fd8fc 100644 --- a/snippets/app-rx/build.gradle.kts +++ b/snippets/app-rx/build.gradle.kts @@ -78,8 +78,8 @@ dependencies { // It is recommended to also include the latest Maps SDK, Places SDK and RxJava so you // have the latest features and bug fixes. - implementation("com.google.android.gms:play-services-maps:19.2.0") - implementation("com.google.android.libraries.places:places:4.4.1") + implementation("com.google.android.gms:play-services-maps:20.0.0") + implementation("com.google.android.libraries.places:places:5.3.0") implementation("io.reactivex.rxjava3:rxjava:3.1.12") // [START_EXCLUDE silent] diff --git a/snippets/app-utils-ktx/build.gradle.kts b/snippets/app-utils-ktx/build.gradle.kts index dc93bcaf8..3ef852ef1 100644 --- a/snippets/app-utils-ktx/build.gradle.kts +++ b/snippets/app-utils-ktx/build.gradle.kts @@ -16,18 +16,17 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } android { namespace = "com.example.app_utils_ktx" - compileSdk = 36 + compileSdk = libs.versions.compileSdk.get().toInt() defaultConfig { applicationId = "com.example.app_utils_ktx" minSdk = 23 - targetSdk = 36 + targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 versionName = libs.versions.versionName.get() diff --git a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Clustering.kt b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Clustering.kt index d219f6884..9125bac1e 100644 --- a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Clustering.kt +++ b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Clustering.kt @@ -29,35 +29,11 @@ internal class Clustering { inner class MyItem( lat: Double, lng: Double, - title: String, - snippet: String + override val title: String, + override val snippet: String ) : ClusterItem { - - private val position: LatLng - private val title: String - private val snippet: String - - override fun getPosition(): LatLng { - return position - } - - override fun getTitle(): String { - return title - } - - override fun getSnippet(): String { - return snippet - } - - override fun getZIndex(): Float { - return 0f - } - - init { - position = LatLng(lat, lng) - this.title = title - this.snippet = snippet - } + override val position: LatLng = LatLng(lat, lng) + override val zIndex: Float? = 0f } // [END maps_android_utils_clustering_cluster_item] diff --git a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/GeoJSON.kt b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/GeoJSON.kt index 52f284216..911500d17 100644 --- a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/GeoJSON.kt +++ b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/GeoJSON.kt @@ -35,7 +35,7 @@ internal class GeoJSON { // [START_EXCLUDE silent] null // [END_EXCLUDE] - val layer = GeoJsonLayer(map, geoJsonData) + val layer = GeoJsonLayer(map, geoJsonData!!) // [END maps_android_util_geojson_add_jsonobject] } @@ -90,10 +90,10 @@ internal class GeoJSON { // [END maps_android_util_geojson_geometry_click_events] // [START maps_android_util_geojson_style] - val pointStyle = layer.defaultPointStyle - pointStyle.isDraggable = true - pointStyle.title = "Hello, World!" - pointStyle.snippet = "I am a draggable marker" + val pointStyle = layer.getDefaultPointStyle() + pointStyle.setDraggable(true) + pointStyle.setTitle("Hello, World!") + pointStyle.setSnippet("I am a draggable marker") // [END maps_android_util_geojson_style] // [START maps_android_util_geojson_style_specific] diff --git a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt index a26f190d4..bbc9e09cb 100644 --- a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt +++ b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt @@ -42,7 +42,7 @@ internal class KML { // [START_EXCLUDE silent] null // [END_EXCLUDE] - val layer = KmlLayer(map, inputStream, context) + val layer = KmlLayer(map, inputStream!!, context) // [END maps_android_utils_kml_add_input_stream] // [START maps_android_utils_kml_add_layer] @@ -54,21 +54,21 @@ internal class KML { // [END maps_android_utils_kml_remove_layer] // [START maps_android_utils_kml_access_containers] - for (containers in layer.containers) { + for (containers in layer.getContainers()) { // Do something to container } // [END maps_android_utils_kml_access_containers] // [START maps_android_utils_kml_access_placemarks] - for (placemark in layer.placemarks) { + for (placemark in layer.getPlacemarks()) { // Do something to Placemark } // [END maps_android_utils_kml_access_placemarks] // [START maps_android_utils_kml_access_properties] - for (container in layer.containers) { + for (container in layer.getContainers()) { if (container.hasProperty("name")) { - Log.i("KML", container.getProperty("name")) + Log.i("KML", container.getProperty("name")!!) } } // [END maps_android_utils_kml_access_properties] @@ -78,7 +78,7 @@ internal class KML { layer.setOnFeatureClickListener { feature -> Log.i( "KML", - "Feature clicked: " + feature.id + "Feature clicked: " + feature.getId() ) } // [END maps_android_utils_kml_click_listener] @@ -88,7 +88,7 @@ internal class KML { fun accessContainers(containers: Iterable) { for (container in containers) { if (container.hasContainers()) { - accessContainers(container.containers) + accessContainers(container.getContainers()) } } } // [END maps_android_utils_kml_access_containers_nested] diff --git a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt index 218139c44..f87024baa 100644 --- a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt +++ b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt @@ -50,11 +50,11 @@ internal class Multilayer { // [START maps_android_utils_multilayer_manager] val clusterManager = - ClusterManager(context, map, markerManager) + ClusterManager(context!!, map, markerManager) val geoJsonLineLayer = GeoJsonLayer( map, R.raw.geojson_file, - context, + context!!, markerManager, polygonManager, polylineManager, @@ -63,7 +63,7 @@ internal class Multilayer { val kmlPolylineLayer = KmlLayer( map, R.raw.kml_file, - context, + context!!, markerManager, polygonManager, polylineManager, @@ -108,33 +108,10 @@ internal class Multilayer { inner class MyItem( lat: Double, lng: Double, - title: String, - snippet: String - ) : - ClusterItem { - private val position: LatLng - private val title: String - private val snippet: String - override fun getPosition(): LatLng { - return position - } - - override fun getTitle(): String { - return title - } - - override fun getSnippet(): String { - return snippet - } - - override fun getZIndex(): Float { - return 0f - } - - init { - position = LatLng(lat, lng) - this.title = title - this.snippet = snippet - } + override val title: String, + override val snippet: String + ) : ClusterItem { + override val position: LatLng = LatLng(lat, lng) + override val zIndex: Float? = 0f } } diff --git a/snippets/app-utils/build.gradle.kts b/snippets/app-utils/build.gradle.kts index 967da3892..4f2da83c9 100644 --- a/snippets/app-utils/build.gradle.kts +++ b/snippets/app-utils/build.gradle.kts @@ -16,7 +16,6 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } @@ -80,10 +79,23 @@ dependencies { androidTestImplementation(libs.espresso.core) // [END_EXCLUDE] + // Modern Android projects use version catalogs to manage dependencies. To include the Maps Utility library, + // first add the following to your gradle/libs.versions.toml file: + // + // [versions] + // mapsUtils = "5.1.1" + // + // [libraries] + // maps-utils = { module = "com.google.maps.android:android-maps-utils", version.ref = "mapsUtils" } + // // Utility Library for Maps SDK for Android // You do not need to add a separate dependency for the Maps SDK for Android // since this library builds in the compatible version of the Maps SDK. - implementation("com.google.maps.android:android-maps-utils:3.19.0") + implementation(libs.maps.utils) + + // If your project does not use a version catalog, you can use the following dependency instead: + // + // implementation("com.google.maps.android:android-maps-utils:5.1.1") } // [END maps_android_utils_install_snippet] diff --git a/snippets/app-utils/src/main/java/com/example/app_utils/GeoJSON.java b/snippets/app-utils/src/main/java/com/example/app_utils/GeoJSON.java index b949da695..7c9117f3c 100644 --- a/snippets/app-utils/src/main/java/com/example/app_utils/GeoJSON.java +++ b/snippets/app-utils/src/main/java/com/example/app_utils/GeoJSON.java @@ -41,7 +41,7 @@ class GeoJSON { private GoogleMap map; - private void addGeoJsonLayerJsonObject() { + private void addGeoJsonLayerJsonObject() throws JSONException { // [START maps_android_util_geojson_add_jsonobject] JSONObject geoJsonData = // JSONObject containing the GeoJSON data // [START_EXCLUDE silent] diff --git a/snippets/app/build.gradle.kts b/snippets/app/build.gradle.kts index 940375905..bf8744be5 100644 --- a/snippets/app/build.gradle.kts +++ b/snippets/app/build.gradle.kts @@ -18,7 +18,6 @@ plugins { // [START_EXCLUDE] alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) // [END_EXCLUDE] alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/build.gradle.kts b/snippets/build.gradle.kts index 7c6c417cb..8416d232f 100644 --- a/snippets/build.gradle.kts +++ b/snippets/build.gradle.kts @@ -17,7 +17,6 @@ // [START maps_android_secrets_gradle_plugin_project_level_config] plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.secrets.gradle.plugin) apply false } diff --git a/tutorials/kotlin/Polygons/app/build.gradle.kts b/tutorials/kotlin/Polygons/app/build.gradle.kts index 0078e4151..d326d26ff 100644 --- a/tutorials/kotlin/Polygons/app/build.gradle.kts +++ b/tutorials/kotlin/Polygons/app/build.gradle.kts @@ -18,7 +18,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") - id("org.jetbrains.kotlin.android") id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") } @@ -41,7 +40,7 @@ android { buildTypes { release { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } From 724cfe0821c5237e68edd6521644ba73dd0fa6f1 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:14:30 -0600 Subject: [PATCH 03/17] docs: add guidelines for AI-assisted contributions (#2402) Add AI-assisted contribution guidelines to CONTRIBUTING.md outlining expectations for ownership, quality standards (idiomatic Kotlin and Android architectural standards), CLA compliance, authorship rules, and prohibiting unsolicited automated pull requests and mass issues. --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da3b1fd84..850f10fe3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,3 +18,7 @@ Please fill out either the individual or corporate Contributor License Agreement Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. + +## AI-Assisted Contributions + +We welcome contributions generated or assisted by AI/LLM tools, provided you adhere to our project guidelines and maintain full ownership of your work. As the submitting author, you assume complete responsibility and accountability for all submitted code, documentation, tests, and pull request descriptions. Please thoroughly review, test, and validate all generated output—ensuring it follows idiomatic Kotlin and Android architectural standards—before submitting. Contributions must comply with the standard Google Contributor License Agreement (CLA), AI tools must not be listed as authors or co-authors on commits or PRs, and running automated agents to submit mass issues, scripted linters, or unsolicited, bot-generated pull requests without prior human discussion is strictly prohibited. From b9447b3943e0b9ad322236b95e37f05bb9d3de64 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:05:02 -0600 Subject: [PATCH 04/17] fix(snippets,tutorials): improve null safety, Compose animations, and update dependencies - Bump playServicesLocation to 21.4.0 in gradle/libs.versions.toml - Refactor FireMarkers animation state with rememberUpdatedMarkerState - Fix null safety and field initialization in KML.kt and Multilayer.kt - Clean up test runners and string templates in tutorial samples --- .../com/example/firemarkers/MainActivity.kt | 22 +++++++++++-------- gradle/libs.versions.toml | 2 ++ snippets/app-compose/build.gradle.kts | 3 ++- snippets/app-ktx/build.gradle.kts | 3 ++- .../java/com/example/app_utils_ktx/KML.kt | 4 ++-- .../com/example/app_utils_ktx/Multilayer.kt | 14 ++++++------ .../com/google/maps/example/TileOverlays.java | 5 +++-- tutorials/java/Polygons/app/build.gradle.kts | 4 ++-- .../app/build.gradle.kts | 4 ++-- .../kotlin/Polygons/app/build.gradle.kts | 4 ++-- .../java/com/example/polygons/PolyActivity.kt | 4 ++-- 11 files changed, 39 insertions(+), 30 deletions(-) diff --git a/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt b/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt index a970ef38a..fa9689476 100644 --- a/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt +++ b/FireMarkers/app/src/main/java/com/example/firemarkers/MainActivity.kt @@ -1,4 +1,4 @@ -// Copyright 2025 Google LLC +// 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. @@ -36,6 +36,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.sp @@ -47,8 +48,8 @@ import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.Marker -import com.google.maps.android.compose.MarkerState import com.google.maps.android.compose.rememberCameraPositionState +import com.google.maps.android.compose.rememberUpdatedMarkerState import dagger.hilt.android.AndroidEntryPoint import androidx.core.view.WindowCompat @@ -149,13 +150,16 @@ fun MapScreen(modifier: Modifier = Modifier) { cameraPositionState = cameraPositionState ) { markers.forEach { markerData -> - Marker( - state = remember(markerData.id) { - MarkerState(position = LatLng(markerData.latitude, markerData.longitude)) - }, - title = markerData.label, - icon = BitmapDescriptorFactory.defaultMarker(markerData.color) - ) + key(markerData.id) { + val markerState = rememberUpdatedMarkerState( + position = LatLng(markerData.latitude, markerData.longitude) + ) + Marker( + state = markerState, + title = markerData.label, + icon = BitmapDescriptorFactory.defaultMarker(markerData.color) + ) + } } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 189e0dc0c..0f2eeee37 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ mapsCompose = "8.4.0" mapsKtx = "6.3.0" mapsUtils = "5.1.1" places = "5.3.0" +playServicesLocation = "21.4.0" playServicesMaps = "20.0.0" secretsGradlePlugin = "2.0.1" @@ -106,6 +107,7 @@ maps-ktx = { group = "com.google.maps.android", name = "maps-ktx", version.ref = maps-utils = { module = "com.google.maps.android:android-maps-utils", version.ref = "mapsUtils" } maps-utils-ktx = { group = "com.google.maps.android", name = "maps-utils-ktx", version.ref = "mapsKtx" } places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } +play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" } play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" } # Wear OS diff --git a/snippets/app-compose/build.gradle.kts b/snippets/app-compose/build.gradle.kts index 129c8e19d..83f1bb505 100644 --- a/snippets/app-compose/build.gradle.kts +++ b/snippets/app-compose/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -36,6 +36,7 @@ android { buildTypes { release { isMinifyEnabled = true + isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" diff --git a/snippets/app-ktx/build.gradle.kts b/snippets/app-ktx/build.gradle.kts index e305a70b0..6abf9419b 100644 --- a/snippets/app-ktx/build.gradle.kts +++ b/snippets/app-ktx/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -42,6 +42,7 @@ android { buildTypes { release { isMinifyEnabled = true + isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" diff --git a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt index bbc9e09cb..f340ecaac 100644 --- a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt +++ b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/KML.kt @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -68,7 +68,7 @@ internal class KML { // [START maps_android_utils_kml_access_properties] for (container in layer.getContainers()) { if (container.hasProperty("name")) { - Log.i("KML", container.getProperty("name")!!) + Log.i("KML", container.getProperty("name") ?: "") } } // [END maps_android_utils_kml_access_properties] diff --git a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt index f87024baa..d05a0d9e1 100644 --- a/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt +++ b/snippets/app-utils-ktx/src/main/java/com/example/app_utils_ktx/Multilayer.kt @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -35,26 +35,26 @@ import org.xmlpull.v1.XmlPullParserException import java.io.IOException internal class Multilayer { - private val map: GoogleMap? = null - private val context: Context? = null + private lateinit var map: GoogleMap + private lateinit var context: Context @Suppress("IndexOutOfBoundsException") @Throws(IOException::class, JSONException::class, XmlPullParserException::class) private fun init() { // [START maps_android_utils_multilayer_init] val markerManager = MarkerManager(map) - val groundOverlayManager = GroundOverlayManager(map!!) + val groundOverlayManager = GroundOverlayManager(map) val polygonManager = PolygonManager(map) val polylineManager = PolylineManager(map) // [END maps_android_utils_multilayer_init] // [START maps_android_utils_multilayer_manager] val clusterManager = - ClusterManager(context!!, map, markerManager) + ClusterManager(context, map, markerManager) val geoJsonLineLayer = GeoJsonLayer( map, R.raw.geojson_file, - context!!, + context, markerManager, polygonManager, polylineManager, @@ -63,7 +63,7 @@ internal class Multilayer { val kmlPolylineLayer = KmlLayer( map, R.raw.kml_file, - context!!, + context, markerManager, polygonManager, polylineManager, diff --git a/snippets/app/src/main/java/com/google/maps/example/TileOverlays.java b/snippets/app/src/main/java/com/google/maps/example/TileOverlays.java index 82a19879d..00ae4b574 100644 --- a/snippets/app/src/main/java/com/google/maps/example/TileOverlays.java +++ b/snippets/app/src/main/java/com/google/maps/example/TileOverlays.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -23,6 +23,7 @@ import java.net.MalformedURLException; import java.net.URL; +import java.util.Locale; class TileOverlays implements OnMapReadyCallback { // [START maps_android_tile_overlays_add] @@ -34,7 +35,7 @@ class TileOverlays implements OnMapReadyCallback { public URL getTileUrl(int x, int y, int zoom) { /* Define the URL pattern for the tile images */ - String s = String.format("http://my.image.server/images/%d/%d/%d.png", zoom, x, y); + String s = String.format(Locale.US, "http://my.image.server/images/%d/%d/%d.png", zoom, x, y); if (!checkTileExists(x, y, zoom)) { return null; diff --git a/tutorials/java/Polygons/app/build.gradle.kts b/tutorials/java/Polygons/app/build.gradle.kts index a8ec2ad89..64daa8270 100644 --- a/tutorials/java/Polygons/app/build.gradle.kts +++ b/tutorials/java/Polygons/app/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -28,7 +28,7 @@ android { targetSdk = libs.versions.targetSdk.get().toInt() versionCode = libs.versions.versionCode.get().toInt() versionName = libs.versions.versionName.get() - testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { diff --git a/tutorials/kotlin/CurrentPlaceDetailsOnMap/app/build.gradle.kts b/tutorials/kotlin/CurrentPlaceDetailsOnMap/app/build.gradle.kts index a3bbb1b3f..7e62390ee 100644 --- a/tutorials/kotlin/CurrentPlaceDetailsOnMap/app/build.gradle.kts +++ b/tutorials/kotlin/CurrentPlaceDetailsOnMap/app/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget /* - * Copyright 2024 Google LLC + * 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. @@ -73,7 +73,7 @@ dependencies { testImplementation(libs.junit) implementation(libs.coreKtx) implementation(libs.lifecycleViewModelKtx) - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${libs.versions.kotlin.get()}") + implementation(libs.kotlin.stdlib) implementation(libs.material) } diff --git a/tutorials/kotlin/Polygons/app/build.gradle.kts b/tutorials/kotlin/Polygons/app/build.gradle.kts index d326d26ff..6658c0193 100644 --- a/tutorials/kotlin/Polygons/app/build.gradle.kts +++ b/tutorials/kotlin/Polygons/app/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget /* - * Copyright 2024 Google LLC + * 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. @@ -30,7 +30,7 @@ android { targetSdk = libs.versions.targetSdk.get().toInt() versionCode = libs.versions.versionCode.get().toInt() versionName = libs.versions.versionName.get() - testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { diff --git a/tutorials/kotlin/Polygons/app/src/main/java/com/example/polygons/PolyActivity.kt b/tutorials/kotlin/Polygons/app/src/main/java/com/example/polygons/PolyActivity.kt index 51f4389e3..ffa64df29 100644 --- a/tutorials/kotlin/Polygons/app/src/main/java/com/example/polygons/PolyActivity.kt +++ b/tutorials/kotlin/Polygons/app/src/main/java/com/example/polygons/PolyActivity.kt @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -185,7 +185,7 @@ class PolyActivity : AppCompatActivity(), OnMapReadyCallback, OnPolylineClickLis // The default pattern is a solid stroke. polyline.pattern = null } - Toast.makeText(this, "Route type " + polyline.tag.toString(), + Toast.makeText(this, "Route type ${polyline.tag}", Toast.LENGTH_SHORT).show() } // [END maps_poly_activity_on_polyline_click] From fdb374f2fafbce4f269e3141a4e422acedc23c5d Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:05:34 -0600 Subject: [PATCH 05/17] fix(WearOS): add safe ambient mode initialization and update copyright headers - Safely wrap AmbientModeSupport.attach() in try/catch across Kotlin and Java activities to guard against missing wearable shared libraries - Align build.gradle.kts and update 2026 copyright headers --- WearOS/Wearable/build.gradle.kts | 17 ++++++++++++----- .../com/example/wearosmap/AmbientActivity.java | 10 +++++++--- .../com/example/wearosmap/MainActivity.java | 10 +++++++--- .../com/example/wearosmap/kt/AmbientActivity.kt | 10 +++++++--- .../com/example/wearosmap/kt/MainActivity.kt | 10 +++++++--- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/WearOS/Wearable/build.gradle.kts b/WearOS/Wearable/build.gradle.kts index 7a6596fdb..b1e2f174b 100644 --- a/WearOS/Wearable/build.gradle.kts +++ b/WearOS/Wearable/build.gradle.kts @@ -45,8 +45,15 @@ android { sarifOutput = layout.buildDirectory.file("reports/lint-results-debug.sarif").get().asFile } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlin { - jvmToolchain(21) + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } } } @@ -90,11 +97,11 @@ dependencies { // compileOnly("com.google.android.wearable:wearable:2.9.0") // implementation("com.google.android.support:wearable:2.9.0") // implementation("com.google.android.gms:play-services-maps:20.0.0") - // implementation("androidx.wear:wear:1.3.0") - // androidTestImplementation("androidx.test.ext:junit:1.1.5") - // androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + // implementation("androidx.wear:wear:1.4.0") + // androidTestImplementation("androidx.test.ext:junit:1.3.0") + // androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") // androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0") - // androidTestImplementation("com.google.truth:truth:1.4.2") + // androidTestImplementation("com.google.truth:truth:1.4.5") // androidTestImplementation("junit:junit:4.13.2") } // [END maps_wear_os_dependencies] diff --git a/WearOS/Wearable/src/main/java/com/example/wearosmap/AmbientActivity.java b/WearOS/Wearable/src/main/java/com/example/wearosmap/AmbientActivity.java index e20e969a3..1d3d4c0cc 100644 --- a/WearOS/Wearable/src/main/java/com/example/wearosmap/AmbientActivity.java +++ b/WearOS/Wearable/src/main/java/com/example/wearosmap/AmbientActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Google Inc. All Rights Reserved. + * 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. @@ -48,8 +48,12 @@ public void onCreate(Bundle savedState) { // Enable ambient support, so the map remains visible in simplified, low-color display // when the user is no longer actively using the app but the app is still visible on the // watch face. - AmbientModeSupport.AmbientController controller = AmbientModeSupport.attach(this); - Log.d(AmbientActivity.class.getSimpleName(), "Is ambient enabled: " + controller.isAmbient()); + try { + AmbientModeSupport.AmbientController controller = AmbientModeSupport.attach(this); + Log.d(AmbientActivity.class.getSimpleName(), "Is ambient enabled: " + controller.isAmbient()); + } catch (Exception e) { + Log.w(AmbientActivity.class.getSimpleName(), "Ambient mode unavailable on this device: " + e.getMessage()); + } // Obtain the MapFragment and set the async listener to be notified when the map is ready. mapFragment = (SupportMapFragment) getSupportFragmentManager() diff --git a/WearOS/Wearable/src/main/java/com/example/wearosmap/MainActivity.java b/WearOS/Wearable/src/main/java/com/example/wearosmap/MainActivity.java index 493292ee0..ebc9ce6da 100644 --- a/WearOS/Wearable/src/main/java/com/example/wearosmap/MainActivity.java +++ b/WearOS/Wearable/src/main/java/com/example/wearosmap/MainActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Google Inc. All Rights Reserved. + * 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. @@ -54,9 +54,13 @@ public void onCreate(Bundle savedState) { // when the user is no longer actively using the app but the app is still visible on the // watch face. // [START maps_wear_os_ambient_mode_support] - AmbientModeSupport.AmbientController controller = AmbientModeSupport.attach(this); + try { + AmbientModeSupport.AmbientController controller = AmbientModeSupport.attach(this); + Log.d(MainActivity.class.getSimpleName(), "Is ambient enabled: " + controller.isAmbient()); + } catch (Exception e) { + Log.w(MainActivity.class.getSimpleName(), "Ambient mode unavailable on this device: " + e.getMessage()); + } // [END maps_wear_os_ambient_mode_support] - Log.d(MainActivity.class.getSimpleName(), "Is ambient enabled: " + controller.isAmbient()); // Retrieve the containers for the root of the layout and the map. Margins will need to be // set on them to account for the system window insets. diff --git a/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/AmbientActivity.kt b/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/AmbientActivity.kt index e14ebf473..9e600e161 100644 --- a/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/AmbientActivity.kt +++ b/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/AmbientActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Google Inc. All Rights Reserved. + * 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. @@ -39,8 +39,12 @@ class AmbientActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackP // Enable ambient support, so the map remains visible in simplified, low-color display // when the user is no longer actively using the app but the app is still visible on the // watch face. - val controller = AmbientModeSupport.attach(this) - Log.d(AmbientActivity::class.java.simpleName, "Is ambient enabled: " + controller.isAmbient) + try { + val controller = AmbientModeSupport.attach(this) + Log.d(AmbientActivity::class.java.simpleName, "Is ambient enabled: " + controller.isAmbient) + } catch (e: Exception) { + Log.w(AmbientActivity::class.java.simpleName, "Ambient mode unavailable on this device: ${e.message}") + } // Obtain the MapFragment and set the async listener to be notified when the map is ready. mapFragment = supportFragmentManager diff --git a/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/MainActivity.kt b/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/MainActivity.kt index b80e4a28d..c3b1ec8e8 100644 --- a/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/MainActivity.kt +++ b/WearOS/Wearable/src/main/kotlin/com/example/wearosmap/kt/MainActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Google Inc. All Rights Reserved. + * 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. @@ -49,9 +49,13 @@ class MainActivity : AppCompatActivity(), OnMapReadyCallback, // when the user is no longer actively using the app but the app is still visible on the // watch face. // [START maps_wear_os_ambient_mode_support] - val controller = AmbientModeSupport.attach(this) + try { + val controller = AmbientModeSupport.attach(this) + Log.d(MainActivity::class.java.simpleName, "Is ambient enabled: " + controller.isAmbient) + } catch (e: Exception) { + Log.w(MainActivity::class.java.simpleName, "Ambient mode unavailable on this device: ${e.message}") + } // [END maps_wear_os_ambient_mode_support] - Log.d(MainActivity::class.java.simpleName, "Is ambient enabled: " + controller.isAmbient) // Retrieve the containers for the root of the layout and the map. Margins will need to be // set on them to account for the system window insets. From 2e71a5436e033abb82aca36c9fa045827966eb3b Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:44:36 -0600 Subject: [PATCH 06/17] fix(ApiDemos): modernize window insets dispatch in SamplesBaseActivity and fix SaveStateDemo - Safely apply system window insets in SamplesBaseActivity (Kotlin & Java) to root containers without double-padding child views - Add save_state_demo.xml layout with constraint bindings and wire up SaveStateDemoActivity - Clean up null safety and listener callbacks in OnMapAndViewReadyListener --- .../example/mapdemo/SamplesBaseActivity.java | 67 ++++++++++++-- .../kotlindemos/OnMapAndViewReadyListener.kt | 30 ++++--- .../kotlindemos/SamplesBaseActivity.kt | 88 +++++++++++++++---- .../kotlindemos/SaveStateDemoActivity.kt | 16 ++-- .../src/main/res/layout/save_state_demo.xml | 51 +++++++++++ .../kotlindemos/OnMapAndViewReadyListener.kt | 29 +++--- 6 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 ApiDemos/project/kotlin-app/src/main/res/layout/save_state_demo.xml diff --git a/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/SamplesBaseActivity.java b/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/SamplesBaseActivity.java index 6a1518dcb..fe9969090 100644 --- a/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/SamplesBaseActivity.java +++ b/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/SamplesBaseActivity.java @@ -1,4 +1,4 @@ -// Copyright 2025 Google LLC +// 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. @@ -18,6 +18,8 @@ import android.os.Bundle; import android.view.View; +import android.view.ViewGroup; + import androidx.activity.EdgeToEdge; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -33,18 +35,67 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { EdgeToEdge.enable(this); } + @Override + public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + setupEdgeToEdgeInsets(); + } + + @Override + public void setContentView(View view) { + super.setContentView(view); + setupEdgeToEdgeInsets(); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + super.setContentView(view, params); + setupEdgeToEdgeInsets(); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + super.addContentView(view, params); + setupEdgeToEdgeInsets(); + } + + private void setupEdgeToEdgeInsets() { + View root = findViewById(android.R.id.content); + if (root == null) return; + View topBar = root.findViewById(com.example.common_ui.R.id.top_bar); + if (topBar != null) { + android.util.TypedValue typedValue = new android.util.TypedValue(); + int baseHeight; + if (getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) { + baseHeight = android.util.TypedValue.complexToDimensionPixelSize(typedValue.data, getResources().getDisplayMetrics()); + } else { + baseHeight = (int) (56 * getResources().getDisplayMetrics().density); + } + ViewCompat.setOnApplyWindowInsetsListener(topBar, (view, insets) -> { + Insets statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.displayCutout()); + view.setPadding(statusBar.left, statusBar.top, statusBar.right, 0); + view.getLayoutParams().height = baseHeight + statusBar.top; + view.requestLayout(); + return insets; + }); + } + + View mapContainer = root.findViewById(com.example.common_ui.R.id.map_container); + View bottomTarget = mapContainer != null ? mapContainer : root; + ViewCompat.setOnApplyWindowInsetsListener(bottomTarget, (view, insets) -> { + Insets navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars() | WindowInsetsCompat.Type.displayCutout()); + int topInsets = (topBar == null) ? insets.getInsets(WindowInsetsCompat.Type.statusBars()).top : 0; + view.setPadding(navBars.left, topInsets, navBars.right, navBars.bottom); + return insets; + }); + } + /** * Applies insets to the container view to properly handle window insets. * * @param container the container view to apply insets to */ protected static void applyInsets(View container) { - ViewCompat.setOnApplyWindowInsetsListener(container, - (view, insets) -> { - Insets innerPadding = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); - view.setPadding(innerPadding.left, innerPadding.top, innerPadding.right, innerPadding.bottom); - return insets; - } - ); + // Handled automatically in SamplesBaseActivity } } \ No newline at end of file diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt index 58f018c46..ad6ec6b43 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google LLC + * 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. @@ -36,7 +36,7 @@ class OnMapAndViewReadyListener( ) : OnGlobalLayoutListener, OnMapReadyCallback { - private val mapView: View? = mapFragment.view + private var mapView: View? = null private var isViewReady = false private var isMapReady = false @@ -52,16 +52,6 @@ class OnMapAndViewReadyListener( } private fun registerListeners() { - // View layout. - mapView?.let { - if (it.width != 0 && it.height != 0) { - // View has already completed layout. - isViewReady = true - } else { - // Map has not undergone layout, register a View observer. - it.viewTreeObserver.addOnGlobalLayoutListener(this) - } - } // GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later. mapFragment.getMapAsync(this) } @@ -70,6 +60,22 @@ class OnMapAndViewReadyListener( // NOTE: The GoogleMap API specifies the listener is removed just prior to invocation. map = googleMap isMapReady = true + + // View layout. + mapView = mapFragment.view + val view = mapView + if (view != null) { + if (view.width != 0 && view.height != 0) { + // View has already completed layout. + isViewReady = true + } else { + // Map has not undergone layout, register a View observer. + view.viewTreeObserver.addOnGlobalLayoutListener(this) + } + } else { + isViewReady = true + } + fireCallbackIfReady() } diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SamplesBaseActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SamplesBaseActivity.kt index a6982e167..e9d9d2ecf 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SamplesBaseActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SamplesBaseActivity.kt @@ -1,4 +1,4 @@ -// Copyright 2025 Google LLC +// 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. @@ -15,9 +15,9 @@ package com.example.kotlindemos import android.os.Bundle import android.view.View +import android.view.ViewGroup import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -27,27 +27,81 @@ open class SamplesBaseActivity : AppCompatActivity() { enableEdgeToEdge() } + override fun setContentView(layoutResID: Int) { + super.setContentView(layoutResID) + setupEdgeToEdgeInsets() + } + + override fun setContentView(view: View?) { + super.setContentView(view) + setupEdgeToEdgeInsets() + } + + override fun setContentView(view: View?, params: ViewGroup.LayoutParams?) { + super.setContentView(view, params) + setupEdgeToEdgeInsets() + } + + override fun addContentView(view: View?, params: ViewGroup.LayoutParams?) { + super.addContentView(view, params) + setupEdgeToEdgeInsets() + } + + private fun setupEdgeToEdgeInsets() { + val root = findViewById(android.R.id.content) ?: return + val topBar = root.findViewById(com.example.common_ui.R.id.top_bar) + if (topBar != null) { + val typedValue = android.util.TypedValue() + val baseHeight = if (theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) { + android.util.TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics) + } else { + (56 * resources.displayMetrics.density).toInt() + } + ViewCompat.setOnApplyWindowInsetsListener(topBar) { view, insets -> + val statusBar = insets.getInsets( + WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout() + ) + view.setPadding( + statusBar.left, + statusBar.top, + statusBar.right, + 0 + ) + view.layoutParams.height = baseHeight + statusBar.top + view.requestLayout() + insets + } + } + + val mapContainer = root.findViewById(com.example.common_ui.R.id.map_container) + val bottomTarget = mapContainer ?: root + ViewCompat.setOnApplyWindowInsetsListener(bottomTarget) { view, insets -> + val navBars = insets.getInsets( + WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.displayCutout() + ) + val topInsets = if (topBar == null) { + insets.getInsets(WindowInsetsCompat.Type.statusBars()).top + } else { + 0 + } + view.setPadding( + navBars.left, + topInsets, + navBars.right, + navBars.bottom + ) + insets + } + } + companion object { /** * Applies insets to the container view to properly handle window insets. * * @param container the container view to apply insets to */ - fun applyInsets(container: View) { - ViewCompat.setOnApplyWindowInsetsListener( - container, - OnApplyWindowInsetsListener { view: View?, insets: WindowInsetsCompat? -> - val innerPadding = - insets!!.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()) - view!!.setPadding( - innerPadding.left, - innerPadding.top, - innerPadding.right, - innerPadding.bottom - ) - insets - } - ) + fun applyInsets(container: View? = null) { + // Handled automatically in SamplesBaseActivity } } } \ No newline at end of file diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SaveStateDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SaveStateDemoActivity.kt index 07cbd9110..a9b116003 100755 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SaveStateDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/SaveStateDemoActivity.kt @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -16,6 +16,7 @@ package com.example.kotlindemos import android.os.Bundle import android.os.Parcelable +import androidx.core.os.BundleCompat import androidx.lifecycle.lifecycleScope import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener @@ -26,7 +27,8 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Marker import com.google.maps.android.ktx.addMarker import com.google.maps.android.ktx.awaitMap -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize +import kotlinx.coroutines.launch import java.util.Random /** @@ -73,13 +75,15 @@ class SaveStateDemoActivity : SamplesBaseActivity() { // the savedInsanceState Bundle. // - Custom Parcelable objects were wrapped in another Bundle. mMarkerPosition = - savedInstanceState?.getParcelable(MARKER_POSITION) ?: DEFAULT_MARKER_POSITION + savedInstanceState?.let { BundleCompat.getParcelable(it, MARKER_POSITION, LatLng::class.java) } + ?: DEFAULT_MARKER_POSITION mMarkerInfo = - savedInstanceState?.getBundle(OTHER_OPTIONS)?.getParcelable(MARKER_INFO) ?: MarkerInfo( - BitmapDescriptorFactory.HUE_RED) + savedInstanceState?.getBundle(OTHER_OPTIONS)?.let { + BundleCompat.getParcelable(it, MARKER_INFO, MarkerInfo::class.java) + } ?: MarkerInfo(BitmapDescriptorFactory.HUE_RED) mMoveCameraToMarker = savedInstanceState == null - lifecycleScope.launchWhenCreated { + lifecycleScope.launch { val map = awaitMap() map.addMarker { icon(BitmapDescriptorFactory.defaultMarker(mMarkerInfo.hue)) diff --git a/ApiDemos/project/kotlin-app/src/main/res/layout/save_state_demo.xml b/ApiDemos/project/kotlin-app/src/main/res/layout/save_state_demo.xml new file mode 100644 index 000000000..4291e256d --- /dev/null +++ b/ApiDemos/project/kotlin-app/src/main/res/layout/save_state_demo.xml @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt b/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt index decf452c9..d5cd25ece 100644 --- a/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt +++ b/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt @@ -6,7 +6,7 @@ * corresponding file under the `app/src/gms` directory. */ /* - * Copyright 2018 Google LLC + * 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. @@ -43,7 +43,7 @@ class OnMapAndViewReadyListener( ) : OnGlobalLayoutListener, OnMapReadyCallback { - private val mapView: View? = mapFragment.view + private var mapView: View? = null private var isViewReady = false private var isMapReady = false @@ -59,15 +59,6 @@ class OnMapAndViewReadyListener( } private fun registerListeners() { - // View layout. - if (mapView?.width != 0 && mapView?.height != 0) { - // View has already completed layout. - isViewReady = true - } else { - // Map has not undergone layout, register a View observer. - mapView.viewTreeObserver.addOnGlobalLayoutListener(this) - } - // GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later. mapFragment.getMapAsync(this) } @@ -76,6 +67,22 @@ class OnMapAndViewReadyListener( // NOTE: The GoogleMap API specifies the listener is removed just prior to invocation. map = googleMap ?: return isMapReady = true + + // View layout. + mapView = mapFragment.view + val view = mapView + if (view != null) { + if (view.width != 0 && view.height != 0) { + // View has already completed layout. + isViewReady = true + } else { + // Map has not undergone layout, register a View observer. + view.viewTreeObserver.addOnGlobalLayoutListener(this) + } + } else { + isViewReady = true + } + fireCallbackIfReady() } From 8ab7d4bcb5725fe240221c6b94477644b9d46f84 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:44:43 -0600 Subject: [PATCH 07/17] feat(camera): modernize CameraClamping and VisibleRegion demo activities - Modernize CameraClampingDemoActivity layout with scrollable controls and safe zoom bounds - Clean up null safety and camera animation callbacks in CameraClamping (Kotlin & Java) - Polish VisibleRegionDemoActivity across standard and v3 flavor variants --- .../main/res/layout/camera_clamping_demo.xml | 85 ++++++++++++------- .../src/main/res/layout/camera_demo.xml | 10 +-- .../common-ui/src/main/res/values/strings.xml | 18 ++-- .../mapdemo/CameraClampingDemoActivity.java | 82 +++++++++++------- .../mapdemo/VisibleRegionDemoActivity.java | 8 +- .../mapdemo/VisibleRegionDemoActivity.java | 8 +- .../kotlindemos/CameraClampingDemoActivity.kt | 77 ++++++++++------- .../kotlindemos/VisibleRegionDemoActivity.kt | 5 +- .../kotlindemos/VisibleRegionDemoActivity.kt | 5 +- 9 files changed, 183 insertions(+), 115 deletions(-) diff --git a/ApiDemos/project/common-ui/src/main/res/layout/camera_clamping_demo.xml b/ApiDemos/project/common-ui/src/main/res/layout/camera_clamping_demo.xml index cb7cdd4c2..e790ff236 100644 --- a/ApiDemos/project/common-ui/src/main/res/layout/camera_clamping_demo.xml +++ b/ApiDemos/project/common-ui/src/main/res/layout/camera_clamping_demo.xml @@ -1,5 +1,5 @@ - + Maps SDK for Android Demos (Java) Maps SDK for Android Demos (Java) Maps SDK for Android Demos (Kotlin) @@ -32,12 +32,18 @@ Demonstrates how to use Camera Clamping. Camera Clamping Demo - Clamp to Adelaide - Clamp to Pacific - Reset LatLng Bounds + Adelaide + Pacific + Reset + Bounds: None + Bounds: Adelaide + Bounds: Pacific + Position: (%1$.6f, %2$.6f)\nzoom=%3$.1f, tilt=%4$.1f\u00b0, bearing=%5$.1f\u00b0 MinZoom++ - MaxZoom-- - Reset Zoom Bounds + MaxZoom-- + Reset Zoom + Zoom Limits: %1$.1f – %2$.1f + Zoom Limits: 2.0 – 21.0 Clear Clickable diff --git a/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/CameraClampingDemoActivity.java b/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/CameraClampingDemoActivity.java index ec2214601..44f13aff5 100644 --- a/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/CameraClampingDemoActivity.java +++ b/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/CameraClampingDemoActivity.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -31,13 +31,13 @@ * This shows how to constrain the camera to specific boundaries and zoom levels. */ public class CameraClampingDemoActivity extends SamplesBaseActivity - implements OnMapReadyCallback, OnCameraIdleListener { + implements OnMapReadyCallback, OnCameraIdleListener, GoogleMap.OnCameraMoveListener { private static final String TAG = CameraClampingDemoActivity.class.getSimpleName(); private static final float ZOOM_DELTA = 2.0f; private static final float DEFAULT_MIN_ZOOM = 2.0f; - private static final float DEFAULT_MAX_ZOOM = 22.0f; + private static final float DEFAULT_MAX_ZOOM = 21.0f; static final LatLngBounds ADELAIDE_BOUNDS = new LatLngBounds( new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61)); @@ -71,18 +71,27 @@ protected void onCreate(Bundle savedInstanceState) { mMap = null; resetMinMaxZoom(); + updateZoomLabel(mMinZoom, mMaxZoom); + + binding.zoomRangeSlider.addOnChangeListener((slider, value, fromUser) -> { + java.util.List values = slider.getValues(); + mMinZoom = values.get(0); + mMaxZoom = values.get(1); + updateZoomLabel(mMinZoom, mMaxZoom); + if (fromUser && mMap != null) { + mMap.setMinZoomPreference(mMinZoom); + mMap.setMaxZoomPreference(mMaxZoom); + } + }); binding.clampLatlngAdelaide.setOnClickListener(v -> onClampToAdelaide()); binding.clampLatlngPacific.setOnClickListener(v -> onClampToPacific()); binding.clampLatlngReset.setOnClickListener(v -> onLatLngClampReset()); - binding.clampMinZoom.setOnClickListener(v -> onSetMinZoomClamp()); - binding.clampMaxZoom.setOnClickListener(v -> onSetMaxZoomClamp()); binding.clampZoomReset.setOnClickListener(v -> onMinMaxZoomClampReset()); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(com.example.common_ui.R.id.map); mapFragment.getMapAsync(this); - applyInsets(binding.mapContainer); } @Override @@ -93,12 +102,36 @@ protected void onResume() { @Override public void onMapReady(GoogleMap map) { mMap = map; + map.setOnCameraMoveListener(this); map.setOnCameraIdleListener(this); + updateCameraPosition(); + } + + @Override + public void onCameraMove() { + updateCameraPosition(); } @Override public void onCameraIdle() { - binding.cameraText.setText(mMap.getCameraPosition().toString()); + updateCameraPosition(); + } + + private void updateCameraPosition() { + if (mMap == null) return; + CameraPosition pos = mMap.getCameraPosition(); + binding.cameraText.setText(getString( + com.example.common_ui.R.string.camera_position_format, + pos.target.latitude, + pos.target.longitude, + pos.zoom, + pos.tilt, + pos.bearing + )); + } + + private void updateZoomLabel(float min, float max) { + binding.zoomLabel.setText(getString(com.example.common_ui.R.string.zoom_bounds_label, min, max)); } /** @@ -126,53 +159,40 @@ private void onClampToAdelaide() { if (!checkReady()) { return; } + binding.latlngClampToggleGroup.check(com.example.common_ui.R.id.clamp_latlng_adelaide); mMap.setLatLngBoundsForCameraTarget(ADELAIDE_BOUNDS); mMap.animateCamera(CameraUpdateFactory.newCameraPosition(ADELAIDE_CAMERA)); + binding.clampStatusText.setText(getString(com.example.common_ui.R.string.latlng_clamp_status_adelaide)); } private void onClampToPacific() { if (!checkReady()) { return; } + binding.latlngClampToggleGroup.check(com.example.common_ui.R.id.clamp_latlng_pacific); mMap.setLatLngBoundsForCameraTarget(PACIFIC); mMap.animateCamera(CameraUpdateFactory.newCameraPosition(PACIFIC_CAMERA)); + binding.clampStatusText.setText(getString(com.example.common_ui.R.string.latlng_clamp_status_pacific)); } private void onLatLngClampReset() { if (!checkReady()) { return; } + binding.latlngClampToggleGroup.clearChecked(); // Setting bounds to null removes any previously set bounds. mMap.setLatLngBoundsForCameraTarget(null); + binding.clampStatusText.setText(getString(com.example.common_ui.R.string.latlng_clamp_status_none)); toast("LatLngBounds clamp reset."); } - private void onSetMinZoomClamp() { - if (!checkReady()) { - return; - } - mMinZoom += ZOOM_DELTA; - // Constrains the minimum zoom level. - mMap.setMinZoomPreference(mMinZoom); - toast("Min zoom preference set to: " + mMinZoom); - } - - private void onSetMaxZoomClamp() { - if (!checkReady()) { - return; - } - mMaxZoom -= ZOOM_DELTA; - // Constrains the maximum zoom level. - mMap.setMaxZoomPreference(mMaxZoom); - toast("Max zoom preference set to: " + mMaxZoom); - } - private void onMinMaxZoomClampReset() { - if (!checkReady()) { - return; - } resetMinMaxZoom(); - mMap.resetMinMaxZoomPreference(); + binding.zoomRangeSlider.setValues(DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM); + updateZoomLabel(DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM); + if (mMap != null) { + mMap.resetMinMaxZoomPreference(); + } toast("Min/Max zoom preferences reset."); } } \ No newline at end of file diff --git a/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/VisibleRegionDemoActivity.java b/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/VisibleRegionDemoActivity.java index 6729049eb..bbabb704f 100755 --- a/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/VisibleRegionDemoActivity.java +++ b/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/VisibleRegionDemoActivity.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -16,6 +16,7 @@ import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; import android.view.View; import android.view.animation.Interpolator; @@ -89,7 +90,8 @@ public void onMapReady(GoogleMap map) { // Add a marker to the Opera House. mMap.addMarker(new MarkerOptions().position(SOH).title("Sydney Opera House")); // Add a camera idle listener. - mMap.setOnCameraIdleListener(() -> binding.messageText.setText("CameraChangeListener: " + mMap.getCameraPosition())); + mMap.setOnCameraIdleListener(() -> binding.messageText.setText( + getString(com.example.common_ui.R.string.camera_change_message, mMap.getCameraPosition()))); } /** @@ -147,7 +149,7 @@ public void setMorePadding(View view) { public void animatePadding( final int toLeft, final int toTop, final int toRight, final int toBottom) { - final Handler handler = new Handler(); + final Handler handler = new Handler(Looper.getMainLooper()); final long start = SystemClock.uptimeMillis(); final long duration = 1000; diff --git a/ApiDemos/project/java-app/src/v3/java/com/example/mapdemo/VisibleRegionDemoActivity.java b/ApiDemos/project/java-app/src/v3/java/com/example/mapdemo/VisibleRegionDemoActivity.java index 41ede6037..3e94125be 100755 --- a/ApiDemos/project/java-app/src/v3/java/com/example/mapdemo/VisibleRegionDemoActivity.java +++ b/ApiDemos/project/java-app/src/v3/java/com/example/mapdemo/VisibleRegionDemoActivity.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; import androidx.appcompat.app.AppCompatActivity; import android.view.View; @@ -86,7 +87,8 @@ public void onMapReady(GoogleMap map) { mMap.setOnCameraIdleListener(new OnCameraIdleListener() { @Override public void onCameraIdle() { - mMessageView.setText("CameraChangeListener: " + mMap.getCameraPosition()); + mMessageView.setText(getString( + com.example.common_ui.R.string.camera_change_message, mMap.getCameraPosition())); } }); } @@ -146,7 +148,7 @@ public void setMorePadding(View view) { public void animatePadding( final int toLeft, final int toTop, final int toRight, final int toBottom) { - final Handler handler = new Handler(); + final Handler handler = new Handler(Looper.getMainLooper()); final long start = SystemClock.uptimeMillis(); final long duration = 1000; diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CameraClampingDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CameraClampingDemoActivity.kt index 061b2b126..e55bca42c 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CameraClampingDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CameraClampingDemoActivity.kt @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -42,10 +42,6 @@ class CameraClampingDemoActivity : SamplesBaseActivity() { * Internal min zoom level that can be toggled via the demo. */ private var minZoom = DEFAULT_MIN_ZOOM - - /** - * Internal max zoom level that can be toggled via the demo. - */ private var maxZoom = DEFAULT_MAX_ZOOM @OptIn(ExperimentalCoroutinesApi::class) @@ -53,59 +49,80 @@ class CameraClampingDemoActivity : SamplesBaseActivity() { super.onCreate(savedInstanceState) binding = com.example.common_ui.databinding.CameraClampingDemoBinding.inflate(layoutInflater) setContentView(binding.root) + updateZoomLabel(minZoom, maxZoom) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment - lifecycleScope.launchWhenCreated { + lifecycleScope.launch { map = mapFragment.awaitMap() - launch { - map.cameraIdleEvents().collect { - onCameraIdle() - } + updateCameraPosition() + map.setOnCameraMoveListener { + updateCameraPosition() } - setButtonClickListeners() + map.setOnCameraIdleListener { + updateCameraPosition() + } + setControls() } - applyInsets(binding.mapContainer) } - private fun setButtonClickListeners() { - - binding.clampMinZoom.setOnClickListener { - minZoom += ZOOM_DELTA - // Constrains the minimum zoom level. - map.setMinZoomPreference(minZoom) - toast("Min zoom preference set to: $minZoom") - } - - binding.clampMaxZoom.setOnClickListener { - maxZoom -= ZOOM_DELTA - // Constrains the maximum zoom level. - map.setMaxZoomPreference(maxZoom) - toast("Max zoom preference set to: $maxZoom") + private fun setControls() { + binding.zoomRangeSlider.addOnChangeListener { slider, _, fromUser -> + val values = slider.values + minZoom = values[0] + maxZoom = values[1] + updateZoomLabel(minZoom, maxZoom) + if (fromUser && ::map.isInitialized) { + map.setMinZoomPreference(minZoom) + map.setMaxZoomPreference(maxZoom) + } } binding.clampZoomReset.setOnClickListener { resetMinMaxZoom() - map.resetMinMaxZoomPreference() + binding.zoomRangeSlider.setValues(DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM) + updateZoomLabel(DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM) + if (::map.isInitialized) { + map.resetMinMaxZoomPreference() + } toast("Min/Max zoom preferences reset.") } binding.clampLatlngAdelaide.setOnClickListener { + binding.latlngClampToggleGroup.check(R.id.clamp_latlng_adelaide) map.setLatLngBoundsForCameraTarget(ADELAIDE_BOUNDS) map.animateCamera(CameraUpdateFactory.newCameraPosition(ADELAIDE_CAMERA)) + binding.clampStatusText.text = getString(R.string.latlng_clamp_status_adelaide) } binding.clampLatlngPacific.setOnClickListener { + binding.latlngClampToggleGroup.check(R.id.clamp_latlng_pacific) map.setLatLngBoundsForCameraTarget(PACIFIC) map.animateCamera(CameraUpdateFactory.newCameraPosition(PACIFIC_CAMERA)) + binding.clampStatusText.text = getString(R.string.latlng_clamp_status_pacific) } binding.clampLatlngReset.setOnClickListener { + binding.latlngClampToggleGroup.clearChecked() map.setLatLngBoundsForCameraTarget(null) + binding.clampStatusText.text = getString(R.string.latlng_clamp_status_none) toast("LatLngBounds clamp reset.") } } - private fun onCameraIdle() { - binding.cameraText.text = map.cameraPosition.toString() + private fun updateZoomLabel(min: Float, max: Float) { + binding.zoomLabel.text = getString(R.string.zoom_bounds_label, min, max) + } + + private fun updateCameraPosition() { + if (!::map.isInitialized) return + val pos = map.cameraPosition + binding.cameraText.text = getString( + R.string.camera_position_format, + pos.target.latitude, + pos.target.longitude, + pos.zoom, + pos.tilt, + pos.bearing + ) } private fun toast(msg: String) { @@ -121,7 +138,7 @@ class CameraClampingDemoActivity : SamplesBaseActivity() { private val TAG = CameraClampingDemoActivity::class.java.name private const val ZOOM_DELTA = 2.0f private const val DEFAULT_MIN_ZOOM = 2.0f - private const val DEFAULT_MAX_ZOOM = 22.0f + private const val DEFAULT_MAX_ZOOM = 21.0f val ADELAIDE_BOUNDS = LatLngBounds( LatLng(-35.0, 138.58), LatLng(-34.9, 138.61)) private val ADELAIDE_CAMERA = CameraPosition.Builder() diff --git a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt index 5b4b75fb4..c48db2b93 100644 --- a/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google LLC + * 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. @@ -18,6 +18,7 @@ package com.example.kotlindemos import android.os.Bundle import android.os.Handler +import android.os.Looper import android.os.SystemClock import android.view.View import android.view.animation.OvershootInterpolator @@ -121,7 +122,7 @@ class VisibleRegionDemoActivity : // this function smoothly changes the amount of padding over a period of time private fun animatePadding(toLeft: Int, toTop: Int, toRight: Int, toBottom: Int) { - val handler = Handler() + val handler = Handler(Looper.getMainLooper()) val start = SystemClock.uptimeMillis() val duration: Long = 1000 diff --git a/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt b/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt index f316a9843..30b9c0bad 100644 --- a/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt +++ b/ApiDemos/project/kotlin-app/src/v3/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt @@ -6,7 +6,7 @@ * corresponding file under the `app/src/gms` directory. */ /* - * Copyright 2018 Google LLC + * 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. @@ -25,6 +25,7 @@ package com.example.kotlindemos import android.os.Bundle import android.os.Handler +import android.os.Looper import android.os.SystemClock import android.view.View import android.view.animation.OvershootInterpolator @@ -131,7 +132,7 @@ class VisibleRegionDemoActivity : // this function smoothly changes the amount of padding over a period of time private fun animatePadding(toLeft: Int, toTop: Int, toRight: Int, toBottom: Int) { - val handler = Handler() + val handler = Handler(Looper.getMainLooper()) val start = SystemClock.uptimeMillis() val duration: Long = 1000 From c23ae40f534cbe022c9c64a2323792f40adf55b4 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:44:58 -0600 Subject: [PATCH 08/17] feat(demos): polish Events, LocationSource, Marker, and Layers demo activities - Modernize EventsDemoActivity with Material text view styles and coordinate formatting - Fix toolbar title and custom location provider handling in LocationSourceDemoActivity - Clean up layout constraints, borderless button styling, and checkbox state in MarkerDemo and LayersDemo --- .../src/main/res/layout/events_demo.xml | 11 +- .../src/main/res/layout/layers_demo.xml | 18 +-- .../src/main/res/layout/marker_demo.xml | 123 ++++++++++-------- .../common-ui/src/main/res/values/strings.xml | 3 + .../example/mapdemo/EventsDemoActivity.java | 40 +++++- .../example/mapdemo/LayersDemoActivity.java | 11 +- .../mapdemo/LocationSourceDemoActivity.java | 7 +- .../example/mapdemo/MarkerDemoActivity.java | 11 +- .../example/mapdemo/MarkerDemoActivity.java | 11 +- .../example/kotlindemos/EventsDemoActivity.kt | 34 ++++- .../example/kotlindemos/LayersDemoActivity.kt | 12 +- .../kotlindemos/LocationSourceDemoActivity.kt | 22 ++-- .../example/kotlindemos/MarkerDemoActivity.kt | 16 ++- .../example/kotlindemos/MarkerDemoActivity.kt | 16 ++- 14 files changed, 223 insertions(+), 112 deletions(-) diff --git a/ApiDemos/project/common-ui/src/main/res/layout/events_demo.xml b/ApiDemos/project/common-ui/src/main/res/layout/events_demo.xml index d53a3ed44..709d4b2fc 100644 --- a/ApiDemos/project/common-ui/src/main/res/layout/events_demo.xml +++ b/ApiDemos/project/common-ui/src/main/res/layout/events_demo.xml @@ -1,5 +1,5 @@ - + app:title="@string/marker_demo_label" + app:titleTextColor="?attr/colorOnPrimary" /> + + + + + + + + + + + + + + android:id="@+id/map_frame" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/top_controls"> + android:padding="8dp"> -