From a534800c976d9a49166c3e08d9f1e74ea2eabff8 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:26:32 -0600 Subject: [PATCH 01/17] feat: migrate android-maps-ktx into android-maps-utils (v6.0.0-rc01) (#1716) - Consolidate Kotlin Extensions (KTX into Utils): Move all reactive Coroutine/Flow extensions (awaitMap, mapClickEvents, cameraMoveEvents) and option builder DSLs (addMarker, addPolyline, addPolygon) from android-maps-ktx directly into android-maps-utils. - Canonical Non-KTX Packages: Place all reactive Coroutine/Flow extensions and DSL builders in canonical com.google.maps.android.* packages. - Deprecated Compatibility Layer: Preserve the legacy com.google.maps.android.ktx.* package structure with @Deprecated(level = DeprecationLevel.WARNING, replaceWith = ReplaceWith(...)) forwarding wrappers and typealiases so existing imports compile seamlessly with deprecation warnings. - Multi-Module Integration: Integrate KTX extensions across :library, :clustering, :heatmaps, and :data modules. - Demo & Test Consolidation: Include KtxExtensionsDemoActivity in :demo and integrate all 18 KTX unit test suites with both canonical and shim test coverage. --- .vscode/settings.json | 4 + CHANGELOG.md | 12 +- README.md | 62 +- .../android/clustering/ClusterManagerFlows.kt | 139 ++++ .../maps/android/geometry/PointExtensions.kt | 42 ++ .../ktx/utils/clustering/ClusterManager.kt | 45 ++ .../maps/android/ktx/utils/geometry/Point.kt | 27 + .../clustering/ClusterManagerFlowsTest.kt | 139 ++++ .../android/geometry/PointExtensionsTest.kt | 45 ++ .../utils/clustering/ClusterManagerTest.kt | 140 ++++ .../android/ktx/utils/geometry/PointTest.kt | 46 ++ .../maps/android/data/geojson/GeoJson.kt | 72 ++ .../com/google/maps/android/data/kml/Kml.kt | 76 +++ .../maps/android/ktx/utils/geojson/GeoJson.kt | 49 ++ .../google/maps/android/ktx/utils/kml/Kml.kt | 53 ++ demo/src/main/AndroidManifest.xml | 3 + .../utils/demo/KtxExtensionsDemoActivity.kt | 112 ++++ demo/src/main/res/values/strings.xml | 1 + gradle.properties | 4 + gradle/libs.versions.toml | 4 + .../google/maps/android/heatmaps/Heatmap.kt | 76 +++ .../android/ktx/utils/heatmaps/Heatmap.kt | 48 ++ .../android/heatmaps/HeatmapExtensionsTest.kt | 43 ++ .../android/ktx/utils/heatmaps/HeatmapTest.kt | 44 ++ library/build.gradle.kts | 9 +- library/src/main/AndroidManifest.xml | 3 + .../java/com/google/maps/android/GoogleMap.kt | 545 +++++++++++++++ .../java/com/google/maps/android/LatLng.kt | 215 ++++++ .../com/google/maps/android/MapFragment.kt | 37 + .../java/com/google/maps/android/MapView.kt | 34 + .../maps/android/MapsExperimentalFeature.kt | 23 + .../java/com/google/maps/android/Polygon.kt | 61 ++ .../java/com/google/maps/android/Polyline.kt | 37 + .../android/StreetViewPanoramaFragment.kt | 38 ++ .../maps/android/StreetViewPanoramaView.kt | 102 +++ .../google/maps/android/SupportMapFragment.kt | 37 + .../SupportStreetViewPanoramaFragment.kt | 38 ++ .../maps/android/collections/CircleManager.kt | 31 + .../collections/GroundOverlayManager.kt | 33 + .../maps/android/collections/MarkerManager.kt | 69 ++ .../android/collections/PolygonManager.kt | 33 + .../android/collections/PolylineManager.kt | 33 + .../com/google/maps/android/ktx/GoogleMap.kt | 200 ++++++ .../google/maps/android/ktx/MapFragment.kt | 28 + .../com/google/maps/android/ktx/MapView.kt | 28 + .../android/ktx/MapsExperimentalFeature.kt | 25 + .../android/ktx/StreetViewPanoramaFragment.kt | 28 + .../android/ktx/StreetViewPanoramaView.kt | 64 ++ .../maps/android/ktx/SupportMapFragment.kt | 28 + .../ktx/SupportStreetViewPanoramaFragment.kt | 28 + .../maps/android/ktx/model/CameraPosition.kt | 27 + .../maps/android/ktx/model/CircleOptions.kt | 27 + .../android/ktx/model/GroundOverlayOptions.kt | 27 + .../maps/android/ktx/model/MarkerOptions.kt | 27 + .../maps/android/ktx/model/PolygonOptions.kt | 27 + .../maps/android/ktx/model/PolylineOptions.kt | 27 + .../ktx/model/StreetViewPanoramaCamera.kt | 27 + .../model/StreetViewPanoramaOrientation.kt | 27 + .../android/ktx/model/TileOverlayOptions.kt | 27 + .../google/maps/android/ktx/utils/LatLng.kt | 87 +++ .../google/maps/android/ktx/utils/Polygon.kt | 36 + .../google/maps/android/ktx/utils/Polyline.kt | 28 + .../attribution/AttributionIdInitializer.kt | 39 ++ .../ktx/utils/collection/CircleManager.kt | 36 + .../utils/collection/GroundOverlayManager.kt | 36 + .../ktx/utils/collection/MarkerManager.kt | 42 ++ .../ktx/utils/collection/PolygonManager.kt | 36 + .../ktx/utils/collection/PolylineManager.kt | 36 + .../utils/location/FusedLocationProvider.kt | 43 ++ .../ktx/utils/location/LocationManager.kt | 38 ++ .../android/location/FusedLocationProvider.kt | 87 +++ .../maps/android/location/LocationManager.kt | 121 ++++ .../maps/android/model/CameraPosition.kt | 29 + .../maps/android/model/CircleOptions.kt | 30 + .../android/model/GroundOverlayOptions.kt | 30 + .../maps/android/model/MarkerOptions.kt | 30 + .../maps/android/model/PolygonOptions.kt | 31 + .../maps/android/model/PolylineOptions.kt | 30 + .../android/model/StreetViewPanoramaCamera.kt | 33 + .../model/StreetViewPanoramaOrientation.kt | 33 + .../maps/android/model/TileOverlayOptions.kt | 30 + .../com/google/maps/android/GoogleMapTest.kt | 633 ++++++++++++++++++ .../com/google/maps/android/LatLngTest.kt | 189 ++++++ .../maps/android/MapFragmentExtensionsTest.kt | 437 ++++++++++++ .../com/google/maps/android/PolygonTest.kt | 59 ++ .../com/google/maps/android/PolylineTest.kt | 60 ++ .../collections/CollectionManagersTest.kt | 250 +++++++ .../google/maps/android/ktx/GoogleMapTest.kt | 114 ++++ .../android/ktx/model/CameraPositionTest.kt | 40 ++ .../android/ktx/model/CircleOptionsTest.kt | 48 ++ .../ktx/model/GroundOverlayOptionsTest.kt | 44 ++ .../android/ktx/model/MarkerOptionsTest.kt | 46 ++ .../android/ktx/model/PolygonOptionsTest.kt | 39 ++ .../android/ktx/model/PolylineOptionsTest.kt | 42 ++ .../ktx/model/StreetViewPanoramaCameraTest.kt | 37 + .../StreetViewPanoramaOrientationTest.kt | 35 + .../ktx/model/TileOverlayOptionsTest.kt | 39 ++ .../maps/android/ktx/utils/LatLngTest.kt | 190 ++++++ .../maps/android/ktx/utils/PolygonTest.kt | 60 ++ .../maps/android/ktx/utils/PolylineTest.kt | 61 ++ .../collection/CollectionManagersTest.kt | 251 +++++++ .../location/FusedLocationProviderTest.kt | 117 ++++ .../ktx/utils/location/LocationManagerTest.kt | 150 +++++ .../location/FusedLocationProviderTest.kt | 116 ++++ .../android/location/LocationManagerTest.kt | 149 +++++ .../maps/android/model/CameraPositionTest.kt | 39 ++ .../maps/android/model/CircleOptionsTest.kt | 47 ++ .../android/model/GroundOverlayOptionsTest.kt | 43 ++ .../maps/android/model/MarkerOptionsTest.kt | 45 ++ .../maps/android/model/PolygonOptionsTest.kt | 38 ++ .../maps/android/model/PolylineOptionsTest.kt | 41 ++ .../model/StreetViewPanoramaCameraTest.kt | 36 + .../StreetViewPanoramaOrientationTest.kt | 34 + .../android/model/TileOverlayOptionsTest.kt | 38 ++ .../AttributionIdInitializerTest.kt | 92 ++- 115 files changed, 7909 insertions(+), 27 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt create mode 100644 clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt create mode 100644 clustering/src/main/java/com/google/maps/android/ktx/utils/clustering/ClusterManager.kt create mode 100644 clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt create mode 100644 clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerFlowsTest.kt create mode 100644 clustering/src/test/java/com/google/maps/android/geometry/PointExtensionsTest.kt create mode 100644 clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt create mode 100644 clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt create mode 100644 data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt create mode 100644 data/src/main/java/com/google/maps/android/data/kml/Kml.kt create mode 100644 data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt create mode 100644 data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt create mode 100644 demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt create mode 100644 heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt create mode 100644 heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt create mode 100644 heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapExtensionsTest.kt create mode 100644 heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt create mode 100644 library/src/main/java/com/google/maps/android/GoogleMap.kt create mode 100644 library/src/main/java/com/google/maps/android/LatLng.kt create mode 100644 library/src/main/java/com/google/maps/android/MapFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/MapView.kt create mode 100644 library/src/main/java/com/google/maps/android/MapsExperimentalFeature.kt create mode 100644 library/src/main/java/com/google/maps/android/Polygon.kt create mode 100644 library/src/main/java/com/google/maps/android/Polyline.kt create mode 100644 library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt create mode 100644 library/src/main/java/com/google/maps/android/SupportMapFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/MapFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/MapView.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/MapsExperimentalFeature.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/location/FusedLocationProvider.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt create mode 100644 library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt create mode 100644 library/src/main/java/com/google/maps/android/location/LocationManager.kt create mode 100644 library/src/main/java/com/google/maps/android/model/CameraPosition.kt create mode 100644 library/src/main/java/com/google/maps/android/model/CircleOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/model/GroundOverlayOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/model/MarkerOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/model/PolygonOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/model/PolylineOptions.kt create mode 100644 library/src/main/java/com/google/maps/android/model/StreetViewPanoramaCamera.kt create mode 100644 library/src/main/java/com/google/maps/android/model/StreetViewPanoramaOrientation.kt create mode 100644 library/src/main/java/com/google/maps/android/model/TileOverlayOptions.kt create mode 100644 library/src/test/java/com/google/maps/android/GoogleMapTest.kt create mode 100644 library/src/test/java/com/google/maps/android/LatLngTest.kt create mode 100644 library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/PolygonTest.kt create mode 100644 library/src/test/java/com/google/maps/android/PolylineTest.kt create mode 100644 library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt create mode 100644 library/src/test/java/com/google/maps/android/location/FusedLocationProviderTest.kt create mode 100644 library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/CameraPositionTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/CircleOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/MarkerOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/PolygonOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/PolylineOptionsTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/StreetViewPanoramaCameraTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/StreetViewPanoramaOrientationTest.kt create mode 100644 library/src/test/java/com/google/maps/android/model/TileOverlayOptionsTest.kt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e01206508 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aea68196..cab0cd7c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [6.0.0-rc01](https://github.com/googlemaps/android-maps-utils/compare/v5.2.0...v6.0.0-rc01) (2026-07-13) + +### Features & Breaking Changes (KTX Consolidation) + +* **Consolidated Kotlin Extensions (KTX into Utils)**: Moved all functionality from `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) directly into `android-maps-utils`. + * **Canonical Non-KTX Packages**: All reactive Coroutine/Flow extensions (`awaitMap()`, `mapClickEvents()`, `cameraMoveEvents()`) and option builder DSLs (`addMarker`, `addPolyline`, `addPolygon`) now reside in canonical `android-maps-utils` packages (`com.google.maps.android.*`, `com.google.maps.android.model.*`, `com.google.maps.android.collections.*`, `com.google.maps.android.clustering.*`, `com.google.maps.android.data.geojson.*`, `com.google.maps.android.data.kml.*`, `com.google.maps.android.heatmaps.*`). + * **Deprecated Compatibility Layer**: To ensure full backward compatibility for existing codebases, the legacy `com.google.maps.android.ktx.*` package structure has been preserved with `@Deprecated(level = DeprecationLevel.WARNING, replaceWith = ReplaceWith(...))` forwarding wrappers and typealiases. Existing imports will continue to compile seamlessly with deprecation warnings pointing to the canonical replacements. + * **Demo & Test Consolidation**: Integrated full reactive extension demo (`KtxExtensionsDemoActivity`) into the `:demo` app and migrated all 18 unit test suites from `android-maps-ktx` with both canonical and shim test coverage. + ## [5.2.0](https://github.com/googlemaps/android-maps-utils/compare/v5.1.1...v5.2.0) (2026-08-28) @@ -23,12 +32,10 @@ ## [5.1.0](https://github.com/googlemaps/android-maps-utils/compare/v5.0.0...v5.1.0) (2026-08-05) - ### Features * **data:** add support for parsing GroundOverlay with LatLonQuad bounds ([#1739](https://github.com/googlemaps/android-maps-utils/issues/1739)) ([c2a165d](https://github.com/googlemaps/android-maps-utils/commit/c2a165d03b63cdd12ac8abbd06e9717c854cd4d7)) - ### Bug Fixes * apply polygon styling to GeoJSON MultiPolygon features ([#1727](https://github.com/googlemaps/android-maps-utils/issues/1727)) ([67d7252](https://github.com/googlemaps/android-maps-utils/commit/67d7252e5c1cdf1556b7fc707e80c8372540036c)) @@ -60,6 +67,7 @@ ### Bug Fixes * prevent StackOverflowError when parsing deeply nested KML containers and multi-geometries ([#1710](https://github.com/googlemaps/android-maps-utils/issues/1710)) ([1463cc5](https://github.com/googlemaps/android-maps-utils/commit/1463cc572da85b8a2317690fa94b0e2432995a96)) +>>>>>>> origin/main ## [4.5.1](https://github.com/googlemaps/android-maps-utils/compare/v4.5.0...v4.5.1) (2026-06-17) diff --git a/README.md b/README.md index 5b22a01ea..99953fc82 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ upgrading from 4.x, see the [migration guide](MIGRATION.md). - **Spherical geometry** — for example: computeDistance, computeHeading, computeArea - **Street View metadata** — checks if a Street View panorama exists at a given location +- **Reactive Kotlin Extensions & Builders** — coroutine suspensions (`awaitMap()`), reactive `Flow` observers (`mapClickEvents()`), and option builder DSLs (`addMarker { ... }`) consolidated directly into `com.google.maps.android.*`. -You can also find Kotlin extensions for this library in [Maps Android KTX][android-maps-ktx]. +> [!IMPORTANT] +> **KTX Consolidation Notice (`v6.0.0-rc01+`)**: All Kotlin extensions (`maps-ktx` and `maps-utils-ktx` from `android-maps-ktx`) are now built directly into `android-maps-utils` under the canonical `com.google.maps.android.*` packages. Separate dependencies on `android-maps-ktx` or `maps-utils-ktx` are no longer needed and should be removed. Legacy calls to `com.google.maps.android.ktx.*` packages remain supported via `@Deprecated(level = DeprecationLevel.WARNING)` bridges that forward directly to canonical implementations.

@@ -46,9 +48,7 @@ You can also find Kotlin extensions for this library in [Maps Android KTX][andro ```kotlin dependencies { - // Utilities for Maps SDK for Android (requires Google Play Services) - // 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. + // Utilities and consolidated Kotlin Extensions for Maps SDK for Android // The aggregator artifact transitively pulls in all submodules below. implementation("com.google.maps.android:android-maps-utils:5.2.0") // x-release-please-version } @@ -167,6 +167,60 @@ Full guides for using the utilities are published in +
+ Reactive Kotlin Extensions & Builders (Consolidated in v6.0.0-rc01) + +### Reactive Kotlin Extensions & Builders + +All Kotlin extensions formerly provided by `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) are now integrated into `android-maps-utils` (`v6.0.0-rc01+`) under canonical packages (`com.google.maps.android.*`, `com.google.maps.android.clustering.*`, etc.). + +#### 1. Coroutine Suspensions (`awaitMap()`, `awaitAnimateCamera()`) +```kotlin +import com.google.maps.android.awaitMap +import com.google.maps.android.awaitAnimateCamera + +// Suspend until GoogleMap is ready on MapView / MapFragment +val googleMap: GoogleMap = mapView.awaitMap() + +// Suspend until camera animation completes +googleMap.awaitAnimateCamera(CameraUpdateFactory.newLatLngZoom(sydney, 12f)) +``` + +#### 2. Option Builders DSL (`addMarker`, `addPolyline`, `addPolygon`) +```kotlin +import com.google.maps.android.addMarker +import com.google.maps.android.addCircle + +googleMap.addMarker { + position(LatLng(-33.852, 151.211)) + title("Sydney Opera House") +} + +googleMap.addCircle { + center(LatLng(-33.870, 151.200)) + radius(500.0) + strokeWidth(2f) +} +``` + +#### 3. Reactive `Flow` Observers (`mapClickEvents`, `cameraMoveEvents`) +```kotlin +import com.google.maps.android.mapClickEvents + +lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + googleMap.mapClickEvents().collect { latLng -> + Log.d("MapClick", "Clicked: $latLng") + } + } +} +``` + +#### Backward Compatibility & Deprecation +Existing references to `com.google.maps.android.ktx.*` continue to work through `@Deprecated(level = DeprecationLevel.WARNING)` forwarding wrappers. You can safely migrate your code incrementally to `com.google.maps.android.*`. + +
+
Street View metadata utility diff --git a/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt new file mode 100644 index 000000000..c98819306 --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.clustering + +import com.google.maps.android.clustering.Cluster +import com.google.maps.android.clustering.ClusterItem +import com.google.maps.android.clustering.ClusterManager +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Returns a flow that emits when a cluster is clicked. Using this to observe cluster clicks + * will override an existing listener (if any) to [ClusterManager.setOnClusterClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun ClusterManager.clusterClickEvents(): Flow> = + callbackFlow { + setOnClusterClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnClusterClickListener(null) + } + } + +/** + * Returns a flow that emits when a cluster item is clicked. Using this to observe cluster item clicks + * will override an existing listener (if any) to [ClusterManager.setOnClusterItemClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun ClusterManager.clusterItemClickEvents(): Flow = + callbackFlow { + setOnClusterItemClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnClusterItemClickListener(null) + } + } + +/** + * Returns a flow that emits when a cluster's info window is clicked. Using this to observe cluster info window clicks + * will override an existing listener (if any) to [ClusterManager.setOnClusterInfoWindowClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun ClusterManager.clusterInfoWindowClickEvents(): Flow> = + callbackFlow { + setOnClusterInfoWindowClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnClusterInfoWindowClickListener(null) + } + } + +/** + * Returns a flow that emits when a cluster's info window is long clicked. Using this to observe cluster info window long clicks + * will override an existing listener (if any) to [ClusterManager.setOnClusterInfoWindowLongClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun ClusterManager.clusterInfoWindowLongClickEvents(): Flow> = + callbackFlow { + setOnClusterInfoWindowLongClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnClusterInfoWindowLongClickListener(null) + } + } + +/** + * Returns a flow that emits when a cluster item's info window is clicked. Using this to observe cluster item info window clicks + * will override an existing listener (if any) to [ClusterManager.setOnClusterItemInfoWindowClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun ClusterManager.clusterItemInfoWindowClickEvents(): Flow = + callbackFlow { + setOnClusterItemInfoWindowClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnClusterItemInfoWindowClickListener(null) + } + } + +/** + * Returns a flow that emits when a cluster item's info window is long clicked. Using this to observe cluster item info window long clicks + * will override an existing listener (if any) to [ClusterManager.setOnClusterItemInfoWindowLongClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun ClusterManager.clusterItemInfoWindowLongClickEvents(): Flow = + callbackFlow { + setOnClusterItemInfoWindowLongClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnClusterItemInfoWindowLongClickListener(null) + } + } diff --git a/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt b/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt new file mode 100644 index 000000000..147faea33 --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:Suppress("NOTHING_TO_INLINE") +package com.google.maps.android.geometry + +import com.google.maps.android.geometry.Point + +/** + * Returns the x value of this Point. + * + * e.g. + * + * ``` + * val (x, _) = point + * ``` + */ +public inline operator fun Point.component1(): Double = this.x + +/** + * Returns the y value of this Point. + * + * e.g. + * + * ``` + * val (_, y) = point + */ +public inline operator fun Point.component2(): Double = this.y diff --git a/clustering/src/main/java/com/google/maps/android/ktx/utils/clustering/ClusterManager.kt b/clustering/src/main/java/com/google/maps/android/ktx/utils/clustering/ClusterManager.kt new file mode 100644 index 000000000..ac6d8fd63 --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/ktx/utils/clustering/ClusterManager.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.clustering + +import com.google.maps.android.clustering.Cluster +import com.google.maps.android.clustering.ClusterItem +import com.google.maps.android.clustering.ClusterManager +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.clustering.clusterClickEvents as canonicalClusterClickEvents +import com.google.maps.android.clustering.clusterItemClickEvents as canonicalClusterItemClickEvents +import com.google.maps.android.clustering.clusterInfoWindowClickEvents as canonicalClusterInfoWindowClickEvents +import com.google.maps.android.clustering.clusterInfoWindowLongClickEvents as canonicalClusterInfoWindowLongClickEvents +import com.google.maps.android.clustering.clusterItemInfoWindowClickEvents as canonicalClusterItemInfoWindowClickEvents +import com.google.maps.android.clustering.clusterItemInfoWindowLongClickEvents as canonicalClusterItemInfoWindowLongClickEvents + +@Deprecated("Moved to com.google.maps.android.clustering.clusterClickEvents", ReplaceWith("clusterClickEvents()", "com.google.maps.android.clustering.clusterClickEvents")) +public fun ClusterManager.clusterClickEvents(): Flow> = this.canonicalClusterClickEvents() + +@Deprecated("Moved to com.google.maps.android.clustering.clusterItemClickEvents", ReplaceWith("clusterItemClickEvents()", "com.google.maps.android.clustering.clusterItemClickEvents")) +public fun ClusterManager.clusterItemClickEvents(): Flow = this.canonicalClusterItemClickEvents() + +@Deprecated("Moved to com.google.maps.android.clustering.clusterInfoWindowClickEvents", ReplaceWith("clusterInfoWindowClickEvents()", "com.google.maps.android.clustering.clusterInfoWindowClickEvents")) +public fun ClusterManager.clusterInfoWindowClickEvents(): Flow> = this.canonicalClusterInfoWindowClickEvents() + +@Deprecated("Moved to com.google.maps.android.clustering.clusterInfoWindowLongClickEvents", ReplaceWith("clusterInfoWindowLongClickEvents()", "com.google.maps.android.clustering.clusterInfoWindowLongClickEvents")) +public fun ClusterManager.clusterInfoWindowLongClickEvents(): Flow> = this.canonicalClusterInfoWindowLongClickEvents() + +@Deprecated("Moved to com.google.maps.android.clustering.clusterItemInfoWindowClickEvents", ReplaceWith("clusterItemInfoWindowClickEvents()", "com.google.maps.android.clustering.clusterItemInfoWindowClickEvents")) +public fun ClusterManager.clusterItemInfoWindowClickEvents(): Flow = this.canonicalClusterItemInfoWindowClickEvents() + +@Deprecated("Moved to com.google.maps.android.clustering.clusterItemInfoWindowLongClickEvents", ReplaceWith("clusterItemInfoWindowLongClickEvents()", "com.google.maps.android.clustering.clusterItemInfoWindowLongClickEvents")) +public fun ClusterManager.clusterItemInfoWindowLongClickEvents(): Flow = this.canonicalClusterItemInfoWindowLongClickEvents() diff --git a/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt b/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt new file mode 100644 index 000000000..6741a5750 --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.geometry + +import com.google.maps.android.geometry.Point +import com.google.maps.android.geometry.component1 as canonicalComponent1 +import com.google.maps.android.geometry.component2 as canonicalComponent2 + +@Deprecated("Moved to com.google.maps.android.geometry.component1", ReplaceWith("component1()", "com.google.maps.android.geometry.component1")) +public inline operator fun Point.component1(): Double = this.canonicalComponent1() + +@Deprecated("Moved to com.google.maps.android.geometry.component2", ReplaceWith("component2()", "com.google.maps.android.geometry.component2")) +public inline operator fun Point.component2(): Double = this.canonicalComponent2() diff --git a/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerFlowsTest.kt b/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerFlowsTest.kt new file mode 100644 index 000000000..b7c07ebc9 --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerFlowsTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.clustering + +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.clustering.Cluster +import com.google.maps.android.clustering.ClusterItem +import com.google.maps.android.clustering.ClusterManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class ClusterManagerFlowsTest { + + @Mock + private lateinit var clusterManager: ClusterManager + + @Mock + private lateinit var cluster: Cluster + + @Mock + private lateinit var clusterItem: ClusterItem + + @Captor + private lateinit var clusterClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterItemClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterInfoWindowClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterInfoWindowLongClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterItemInfoWindowClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterItemInfoWindowLongClickListener: ArgumentCaptor> + + @Test + public fun testClusterClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterClickEvents().first() + assertThat(event).isEqualTo(cluster) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterClickListener(clusterClickListener.capture()) + clusterClickListener.value.onClusterClick(cluster) + job.cancel() + } + + @Test + public fun testClusterItemClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterItemClickEvents().first() + assertThat(event).isEqualTo(clusterItem) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemClickListener(clusterItemClickListener.capture()) + clusterItemClickListener.value.onClusterItemClick(clusterItem) + job.cancel() + } + + @Test + public fun testClusterInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterInfoWindowClickEvents().first() + assertThat(event).isEqualTo(cluster) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterInfoWindowClickListener(clusterInfoWindowClickListener.capture()) + clusterInfoWindowClickListener.value.onClusterInfoWindowClick(cluster) + job.cancel() + } + + @Test + public fun testClusterInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterInfoWindowLongClickEvents().first() + assertThat(event).isEqualTo(cluster) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterInfoWindowLongClickListener(clusterInfoWindowLongClickListener.capture()) + clusterInfoWindowLongClickListener.value.onClusterInfoWindowLongClick(cluster) + job.cancel() + } + + @Test + public fun testClusterItemInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterItemInfoWindowClickEvents().first() + assertThat(event).isEqualTo(clusterItem) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemInfoWindowClickListener(clusterItemInfoWindowClickListener.capture()) + clusterItemInfoWindowClickListener.value.onClusterItemInfoWindowClick(clusterItem) + job.cancel() + } + + @Test + public fun testClusterItemInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterItemInfoWindowLongClickEvents().first() + assertThat(event).isEqualTo(clusterItem) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemInfoWindowLongClickListener(clusterItemInfoWindowLongClickListener.capture()) + clusterItemInfoWindowLongClickListener.value.onClusterItemInfoWindowLongClick(clusterItem) + job.cancel() + } +} diff --git a/clustering/src/test/java/com/google/maps/android/geometry/PointExtensionsTest.kt b/clustering/src/test/java/com/google/maps/android/geometry/PointExtensionsTest.kt new file mode 100644 index 000000000..1b660e951 --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/geometry/PointExtensionsTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.geometry + +import com.google.maps.android.geometry.Point +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test + +internal class PointExtensionsTest { + + private lateinit var point: Point + + @Before + fun setUp() { + point = Point(1.0, 2.0) + } + + @Test + fun `destructure x`() { + val (x, _) = point + assertThat(x).isWithin(1e-6).of(1.0) + } + + @Test + fun `destructure y`() { + val (_, y) = point + assertThat(y).isWithin(1e-6).of(2.0) + } +} diff --git a/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt new file mode 100644 index 000000000..3f24783fa --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt @@ -0,0 +1,140 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils.clustering + +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.clustering.Cluster +import com.google.maps.android.clustering.ClusterItem +import com.google.maps.android.clustering.ClusterManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class ClusterManagerTest { + + @Mock + private lateinit var clusterManager: ClusterManager + + @Mock + private lateinit var cluster: Cluster + + @Mock + private lateinit var clusterItem: ClusterItem + + @Captor + private lateinit var clusterClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterItemClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterInfoWindowClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterInfoWindowLongClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterItemInfoWindowClickListener: ArgumentCaptor> + + @Captor + private lateinit var clusterItemInfoWindowLongClickListener: ArgumentCaptor> + + @Test + public fun testClusterClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterClickEvents().first() + assertThat(event).isEqualTo(cluster) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterClickListener(clusterClickListener.capture()) + clusterClickListener.value.onClusterClick(cluster) + job.cancel() + } + + @Test + public fun testClusterItemClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterItemClickEvents().first() + assertThat(event).isEqualTo(clusterItem) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemClickListener(clusterItemClickListener.capture()) + clusterItemClickListener.value.onClusterItemClick(clusterItem) + job.cancel() + } + + @Test + public fun testClusterInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterInfoWindowClickEvents().first() + assertThat(event).isEqualTo(cluster) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterInfoWindowClickListener(clusterInfoWindowClickListener.capture()) + clusterInfoWindowClickListener.value.onClusterInfoWindowClick(cluster) + job.cancel() + } + + @Test + public fun testClusterInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterInfoWindowLongClickEvents().first() + assertThat(event).isEqualTo(cluster) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterInfoWindowLongClickListener(clusterInfoWindowLongClickListener.capture()) + clusterInfoWindowLongClickListener.value.onClusterInfoWindowLongClick(cluster) + job.cancel() + } + + @Test + public fun testClusterItemInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterItemInfoWindowClickEvents().first() + assertThat(event).isEqualTo(clusterItem) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemInfoWindowClickListener(clusterItemInfoWindowClickListener.capture()) + clusterItemInfoWindowClickListener.value.onClusterItemInfoWindowClick(clusterItem) + job.cancel() + } + + @Test + public fun testClusterItemInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = clusterManager.clusterItemInfoWindowLongClickEvents().first() + assertThat(event).isEqualTo(clusterItem) + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemInfoWindowLongClickListener(clusterItemInfoWindowLongClickListener.capture()) + clusterItemInfoWindowLongClickListener.value.onClusterItemInfoWindowLongClick(clusterItem) + job.cancel() + } +} diff --git a/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt b/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt new file mode 100644 index 000000000..55bc151f7 --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt @@ -0,0 +1,46 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils.geometry + +import com.google.maps.android.geometry.Point +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test + +internal class PointTest { + + private lateinit var point: Point + + @Before + fun setUp() { + point = Point(1.0, 2.0) + } + + @Test + fun `destructure x`() { + val (x, _) = point + assertThat(x).isWithin(1e-6).of(1.0) + } + + @Test + fun `destructure y`() { + val (_, y) = point + assertThat(y).isWithin(1e-6).of(2.0) + } +} diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt new file mode 100644 index 000000000..16a56a42a --- /dev/null +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:Suppress("NOTHING_TO_INLINE") +package com.google.maps.android.data.geojson + +import android.content.Context +import androidx.annotation.IntegerRes +import androidx.annotation.RawRes +import com.google.android.gms.maps.GoogleMap +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager +import com.google.maps.android.data.geojson.GeoJsonLayer +import org.json.JSONObject + +/** + * Alias for the [GeoJsonLayer] constructor that provides Kotlin named parameters and default + * values. + */ +public inline fun geoJsonLayer( + map: GoogleMap, + geoJsonFile: JSONObject, + markerManager: MarkerManager? = null, + polygonManager: PolygonManager? = null, + polylineManager: PolylineManager? = null, + groundOverlayManager: GroundOverlayManager? = null +): GeoJsonLayer = GeoJsonLayer( + map, + geoJsonFile, + markerManager, + polygonManager, + polylineManager, + groundOverlayManager +) + +/** + * Alias for the [GeoJsonLayer] constructor that provides Kotlin named parameters and default + * values. + */ +public inline fun geoJsonLayer( + map: GoogleMap, + @RawRes resourceId: Int, + context: Context, + markerManager: MarkerManager? = null, + polygonManager: PolygonManager? = null, + polylineManager: PolylineManager? = null, + groundOverlayManager: GroundOverlayManager? = null +): GeoJsonLayer = GeoJsonLayer( + map, + resourceId, + context, + markerManager, + polygonManager, + polylineManager, + groundOverlayManager +) \ No newline at end of file diff --git a/data/src/main/java/com/google/maps/android/data/kml/Kml.kt b/data/src/main/java/com/google/maps/android/data/kml/Kml.kt new file mode 100644 index 000000000..2bd282a0b --- /dev/null +++ b/data/src/main/java/com/google/maps/android/data/kml/Kml.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:Suppress("NOTHING_TO_INLINE") +package com.google.maps.android.data.kml + +import android.content.Context +import androidx.annotation.RawRes +import com.google.android.gms.maps.GoogleMap +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager +import com.google.maps.android.data.Renderer +import com.google.maps.android.data.kml.KmlLayer +import java.io.InputStream + +/** + * Alias for the [KmlLayer] constructor that provides Kotlin named parameters and default values. + */ +public inline fun kmlLayer( + map: GoogleMap, + @RawRes resourceId: Int, + context: Context, + markerManager: MarkerManager = MarkerManager(map), + polygonManager: PolygonManager = PolygonManager(map), + polylineManager: PolylineManager = PolylineManager(map), + groundOverlayManager: GroundOverlayManager = GroundOverlayManager(map), + imagesCache: Renderer.ImagesCache? = null +): KmlLayer = KmlLayer( + map, + resourceId, + context, + markerManager, + polygonManager, + polylineManager, + groundOverlayManager, + imagesCache +) + +/** + * Alias for the [KmlLayer] constructor that provides Kotlin named parameters and default values. + */ +public inline fun kmlLayer( + map: GoogleMap, + stream: InputStream, + context: Context, + markerManager: MarkerManager = MarkerManager(map), + polygonManager: PolygonManager = PolygonManager(map), + polylineManager: PolylineManager = PolylineManager(map), + groundOverlayManager: GroundOverlayManager = GroundOverlayManager(map), + imagesCache: Renderer.ImagesCache? = null +): KmlLayer = KmlLayer( + map, + stream, + context, + markerManager, + polygonManager, + polylineManager, + groundOverlayManager, + imagesCache +) diff --git a/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt b/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt new file mode 100644 index 000000000..e968ce8d9 --- /dev/null +++ b/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt @@ -0,0 +1,49 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.geojson + +import android.content.Context +import androidx.annotation.RawRes +import com.google.android.gms.maps.GoogleMap +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager +import com.google.maps.android.data.geojson.GeoJsonLayer +import org.json.JSONObject +import com.google.maps.android.data.geojson.geoJsonLayer as canonicalGeoJsonLayer + +@Deprecated("Moved to com.google.maps.android.data.geojson.geoJsonLayer", ReplaceWith("geoJsonLayer(map, geoJsonFile, markerManager, polygonManager, polylineManager, groundOverlayManager)", "com.google.maps.android.data.geojson.geoJsonLayer")) +public inline fun geoJsonLayer( + map: GoogleMap, + geoJsonFile: JSONObject, + markerManager: MarkerManager? = null, + polygonManager: PolygonManager? = null, + polylineManager: PolylineManager? = null, + groundOverlayManager: GroundOverlayManager? = null +): GeoJsonLayer = canonicalGeoJsonLayer(map, geoJsonFile, markerManager, polygonManager, polylineManager, groundOverlayManager) + +@Deprecated("Moved to com.google.maps.android.data.geojson.geoJsonLayer", ReplaceWith("geoJsonLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager)", "com.google.maps.android.data.geojson.geoJsonLayer")) +public inline fun geoJsonLayer( + map: GoogleMap, + @RawRes resourceId: Int, + context: Context, + markerManager: MarkerManager? = null, + polygonManager: PolygonManager? = null, + polylineManager: PolylineManager? = null, + groundOverlayManager: GroundOverlayManager? = null +): GeoJsonLayer = canonicalGeoJsonLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager) diff --git a/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt b/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt new file mode 100644 index 000000000..1fc8fce27 --- /dev/null +++ b/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt @@ -0,0 +1,53 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.kml + +import android.content.Context +import androidx.annotation.RawRes +import com.google.android.gms.maps.GoogleMap +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager +import com.google.maps.android.data.Renderer +import com.google.maps.android.data.kml.KmlLayer +import java.io.InputStream +import com.google.maps.android.data.kml.kmlLayer as canonicalKmlLayer + +@Deprecated("Moved to com.google.maps.android.data.kml.kmlLayer", ReplaceWith("kmlLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache)", "com.google.maps.android.data.kml.kmlLayer")) +public inline fun kmlLayer( + map: GoogleMap, + @RawRes resourceId: Int, + context: Context, + markerManager: MarkerManager = MarkerManager(map), + polygonManager: PolygonManager = PolygonManager(map), + polylineManager: PolylineManager = PolylineManager(map), + groundOverlayManager: GroundOverlayManager = GroundOverlayManager(map), + imagesCache: Renderer.ImagesCache? = null +): KmlLayer = canonicalKmlLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache) + +@Deprecated("Moved to com.google.maps.android.data.kml.kmlLayer", ReplaceWith("kmlLayer(map, stream, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache)", "com.google.maps.android.data.kml.kmlLayer")) +public inline fun kmlLayer( + map: GoogleMap, + stream: InputStream, + context: Context, + markerManager: MarkerManager = MarkerManager(map), + polygonManager: PolygonManager = PolygonManager(map), + polylineManager: PolylineManager = PolylineManager(map), + groundOverlayManager: GroundOverlayManager = GroundOverlayManager(map), + imagesCache: Renderer.ImagesCache? = null +): KmlLayer = canonicalKmlLayer(map, stream, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache) diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 08dd8f099..e79bcc751 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -143,6 +143,9 @@ + diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt new file mode 100644 index 000000000..4531561f5 --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.utils.demo + +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.addMarker +import com.google.maps.android.awaitAnimateCamera +import com.google.maps.android.awaitMap +import com.google.maps.android.ktx.addCircle as deprecatedBridgeAddCircle +import com.google.maps.android.mapClickEvents +import kotlinx.coroutines.launch + +/** + * A demo activity illustrating the consolidated reactive Coroutine/Flow/Builder extensions + * in [com.google.maps.android], as well as compatibility verification for the deprecated + * [com.google.maps.android.ktx] package bridges. + */ +class KtxExtensionsDemoActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + MaterialTheme { + ReactiveMapScreen() + } + } + } + + @Composable + private fun ReactiveMapScreen() { + val context = LocalContext.current + val mapView = remember { MapView(context) } + + LaunchedEffect(mapView) { + mapView.onCreate(Bundle()) + mapView.onStart() + mapView.onResume() + + // 1. Canonical awaitMap() coroutine suspension + val googleMap: GoogleMap = mapView.awaitMap() + + val sydney = LatLng(-33.852, 151.211) + + // 2. Canonical addMarker builder DSL + googleMap.addMarker { + position(sydney) + title("Sydney Opera House (Canonical Builder)") + } + + // 3. Deprecated bridge addCircle check (verifying zero conflicts with canonical builder) + @Suppress("DEPRECATION") + googleMap.deprecatedBridgeAddCircle { + center(LatLng(-33.870, 151.200)) + radius(500.0) + } + + // 4. Canonical awaitAnimateCamera suspension + googleMap.awaitAnimateCamera(CameraUpdateFactory.newLatLngZoom(sydney, 12f), 1500) + + // 5. Canonical Flow observation for map clicks + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + googleMap.mapClickEvents().collect { latLng -> + Toast.makeText( + context, + "Clicked at: ${latLng.latitude}, ${latLng.longitude}", + Toast.LENGTH_SHORT + ).show() + } + } + } + } + + Box(modifier = Modifier.fillMaxSize()) { + AndroidView(factory = { mapView }) + } + } +} diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index d7d914708..fab100c1e 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -76,6 +76,7 @@ IconGenerator Generating tiles AnimationUtil sample + Reactive Extensions & Builders Street View Demo diff --git a/gradle.properties b/gradle.properties index b20dd1f4c..1bbc7354e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,3 +38,7 @@ mavenCentralPassword= # Add a property to enable automatic release to Maven Central (optional, but good for CI) # If true, publishToMavenCentral will also close and release the staging repository mavenCentralAutomaticRelease=false + +VERSION_NAME=6.0.0-rc01 +GROUP=com.google.maps.android + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8215f38f2..6ce64d133 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ compose-bom = "2026.09.00" # --- Google Services (Maps) --- # Versions for Google Play Services libraries essential for map functionality. play-services-maps = "20.0.0" +play-services-location = "21.3.0" navigation-sdk = "7.9.0" desugar-jdk-libs = "2.1.5" @@ -43,6 +44,7 @@ espresso-core = "3.7.0" junit = "4.13.2" kxml2 = "2.3.0" mockito-core = "5.23.0" +mockito-kotlin = "2.2.0" mockk = "1.14.11" robolectric = "4.17" truth = "1.4.5" @@ -98,6 +100,7 @@ material-icons-core = { group = "androidx.compose.material", name = "material-ic # --- Google Services (Maps) --- # Key libraries for integrating Google Maps and related services. play-services-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "play-services-maps" } +play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "play-services-location" } navigation-sdk = { module = "com.google.android.libraries.navigation:navigation", version.ref = "navigation-sdk" } desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar-jdk-libs" } @@ -108,6 +111,7 @@ androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } junit = { module = "junit:junit", version.ref = "junit" } mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-core" } +mockito-kotlin = { module = "com.nhaarman.mockitokotlin2:mockito-kotlin", version.ref = "mockito-kotlin" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } truth = { module = "com.google.truth:truth", version.ref = "truth" } diff --git a/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt new file mode 100644 index 000000000..026cda053 --- /dev/null +++ b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:Suppress("NOTHING_TO_INLINE") +package com.google.maps.android.heatmaps + +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.heatmaps.Gradient +import com.google.maps.android.heatmaps.HeatmapTileProvider +import com.google.maps.android.heatmaps.WeightedLatLng + +/** + * Converts this LatLng to a [WeightedLatLng] + */ +public inline fun LatLng.toWeightedLatLng( + intensity: Double = WeightedLatLng.DEFAULT_INTENSITY +): WeightedLatLng = + WeightedLatLng(this, intensity) + +/** + * Constructs a [HeatmapTileProvider]. + * + * @throws IllegalStateException when [opacity] is not within the range [0, 1] or if [latLngs] is + * empty + */ +public inline fun heatmapTileProviderWithData( + latLngs: Collection, + radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, + gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, + opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY, + maxIntensity: Double = 0.0 +) : HeatmapTileProvider { + return HeatmapTileProvider.Builder() + .data(latLngs) + .radius(radius) + .gradient(gradient) + .opacity(opacity) + .maxIntensity(maxIntensity) + .build() +} + +/** + * Constructs a [HeatmapTileProvider]. + * + * @throws IllegalStateException when [opacity] is not within the range [0, 1] or if [latLngs] is + * empty + */ +public inline fun heatmapTileProviderWithWeightedData( + latLngs: Collection, + radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, + gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, + opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY, + maxIntensity: Double = 0.0 +) : HeatmapTileProvider { + return HeatmapTileProvider.Builder() + .weightedData(latLngs) + .radius(radius) + .gradient(gradient) + .opacity(opacity) + .maxIntensity(maxIntensity) + .build() +} diff --git a/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt b/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt new file mode 100644 index 000000000..25c4dedd9 --- /dev/null +++ b/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt @@ -0,0 +1,48 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.heatmaps + +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.heatmaps.Gradient +import com.google.maps.android.heatmaps.HeatmapTileProvider +import com.google.maps.android.heatmaps.WeightedLatLng +import com.google.maps.android.heatmaps.toWeightedLatLng as canonicalToWeightedLatLng +import com.google.maps.android.heatmaps.heatmapTileProviderWithData as canonicalHeatmapTileProviderWithData +import com.google.maps.android.heatmaps.heatmapTileProviderWithWeightedData as canonicalHeatmapTileProviderWithWeightedData + +@Deprecated("Moved to com.google.maps.android.heatmaps.toWeightedLatLng", ReplaceWith("toWeightedLatLng(intensity)", "com.google.maps.android.heatmaps.toWeightedLatLng")) +public inline fun LatLng.toWeightedLatLng( + intensity: Double = WeightedLatLng.DEFAULT_INTENSITY +): WeightedLatLng = this.canonicalToWeightedLatLng(intensity) + +@Deprecated("Moved to com.google.maps.android.heatmaps.heatmapTileProviderWithData", ReplaceWith("heatmapTileProviderWithData(latLngs, radius, gradient, opacity, maxIntensity)", "com.google.maps.android.heatmaps.heatmapTileProviderWithData")) +public inline fun heatmapTileProviderWithData( + latLngs: Collection, + radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, + gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, + opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY, + maxIntensity: Double = 0.0 +) : HeatmapTileProvider = canonicalHeatmapTileProviderWithData(latLngs, radius, gradient, opacity, maxIntensity) + +@Deprecated("Moved to com.google.maps.android.heatmaps.heatmapTileProviderWithWeightedData", ReplaceWith("heatmapTileProviderWithWeightedData(latLngs, radius, gradient, opacity, maxIntensity)", "com.google.maps.android.heatmaps.heatmapTileProviderWithWeightedData")) +public inline fun heatmapTileProviderWithWeightedData( + latLngs: Collection, + radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, + gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, + opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY, + maxIntensity: Double = 0.0 +) : HeatmapTileProvider = canonicalHeatmapTileProviderWithWeightedData(latLngs, radius, gradient, opacity, maxIntensity) diff --git a/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapExtensionsTest.kt b/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapExtensionsTest.kt new file mode 100644 index 000000000..a58acfccd --- /dev/null +++ b/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapExtensionsTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.heatmaps + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.heatmaps.WeightedLatLng +import com.google.maps.android.heatmaps.toWeightedLatLng +import org.junit.Test + +internal class HeatmapExtensionsTest { + @Test + fun `to WeightedLatLng converts correctly`() { + val latLng = LatLng(1.0, 2.0) + + weightedLatLngEquals(WeightedLatLng(latLng), latLng.toWeightedLatLng()) + weightedLatLngEquals( + WeightedLatLng(latLng, 2.0), + latLng.toWeightedLatLng(intensity = 2.0) + ) + } + + private fun weightedLatLngEquals(lhs: WeightedLatLng, rhs: WeightedLatLng) { + assertThat(lhs.point.x).isWithin(1e-6).of(rhs.point.x) + assertThat(lhs.point.y).isWithin(1e-6).of(rhs.point.y) + assertThat(lhs.intensity).isWithin(1e-6).of(rhs.intensity) + } +} diff --git a/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt b/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt new file mode 100644 index 000000000..4ee394232 --- /dev/null +++ b/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt @@ -0,0 +1,44 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils.heatmaps + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.heatmaps.WeightedLatLng +import com.google.maps.android.ktx.utils.heatmaps.toWeightedLatLng +import org.junit.Test + +internal class HeatmapTest { + @Test + fun `to WeightedLatLng converts correctly`() { + val latLng = LatLng(1.0, 2.0) + + weightedLatLngEquals(WeightedLatLng(latLng), latLng.toWeightedLatLng()) + weightedLatLngEquals( + WeightedLatLng(latLng, 2.0), + latLng.toWeightedLatLng(intensity = 2.0) + ) + } + + private fun weightedLatLngEquals(lhs: WeightedLatLng, rhs: WeightedLatLng) { + assertThat(lhs.point.x).isWithin(1e-6).of(rhs.point.x) + assertThat(lhs.point.y).isWithin(1e-6).of(rhs.point.y) + assertThat(lhs.intensity).isWithin(1e-6).of(rhs.intensity) + } +} diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 88119e578..2b4864d2a 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -63,7 +63,9 @@ android { dependencies { api(libs.play.services.maps) - implementation(libs.kotlinx.coroutines.android) + api(libs.play.services.location) + api(libs.kotlinx.coroutines.android) + api(libs.kotlinx.coroutines.core) implementation(libs.appcompat) implementation(libs.core.ktx) implementation(libs.startup.runtime) @@ -81,6 +83,7 @@ dependencies { testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.robolectric) testImplementation(libs.mockito.core) + testImplementation(libs.mockito.kotlin) } tasks.register("instrumentTest") { @@ -139,3 +142,7 @@ androidComponents { tasks.named("dokkaGeneratePublicationHtml") { dependsOn(generateArtifactIdFile) } + +tasks.named("dokkaGeneratePublicationHtml") { + dependsOn(generateArtifactIdFile) +} diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index d11680548..dd18477c1 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -28,6 +28,9 @@ + diff --git a/library/src/main/java/com/google/maps/android/GoogleMap.kt b/library/src/main/java/com/google/maps/android/GoogleMap.kt new file mode 100644 index 000000000..e0acc4fba --- /dev/null +++ b/library/src/main/java/com/google/maps/android/GoogleMap.kt @@ -0,0 +1,545 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import android.graphics.Bitmap +import android.location.Location +import androidx.annotation.IntDef +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.android.gms.maps.model.IndoorBuilding +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PointOfInterest +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.TileOverlay +import com.google.android.gms.maps.model.TileOverlayOptions +import com.google.maps.android.model.circleOptions +import com.google.maps.android.model.groundOverlayOptions +import com.google.maps.android.model.markerOptions +import com.google.maps.android.model.polygonOptions +import com.google.maps.android.model.polylineOptions +import com.google.maps.android.model.tileOverlayOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +@IntDef( + GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE, + GoogleMap.OnCameraMoveStartedListener.REASON_API_ANIMATION, + GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION +) +@Retention(AnnotationRetention.SOURCE) +public annotation class MoveStartedReason + +public sealed class CameraEvent +public object CameraIdleEvent : CameraEvent() +public object CameraMoveCanceledEvent : CameraEvent() +public object CameraMoveEvent : CameraEvent() +public data class CameraMoveStartedEvent(@param:MoveStartedReason val reason: Int) : CameraEvent() + +/** + * Change event when a marker is dragged. See [GoogleMap.setOnMarkerDragListener] + */ +public sealed class OnMarkerDragEvent { + public abstract val marker: Marker +} + +/** + * Event emitted repeatedly while a marker is being dragged. + */ +public data class MarkerDragEvent(public override val marker: Marker) : OnMarkerDragEvent() + +/** + * Event emitted when a marker has finished being dragged. + */ +public data class MarkerDragEndEvent(public override val marker: Marker) : OnMarkerDragEvent() + +/** + * Event emitted when a marker starts being dragged. + */ +public data class MarkerDragStartEvent(public override val marker: Marker) : OnMarkerDragEvent() + +/** + * Change event when the indoor state changes. See [GoogleMap.OnIndoorStateChangeListener] + */ +public sealed class IndoorChangeEvent + +/** + * Change event when an indoor building is focused. + * See [GoogleMap.OnIndoorStateChangeListener.onIndoorBuildingFocused] + */ +public object IndoorBuildingFocusedEvent : IndoorChangeEvent() + +/** + * Change event when an indoor level is activated. + * See [GoogleMap.OnIndoorStateChangeListener.onIndoorLevelActivated] + */ +public data class IndoorLevelActivatedEvent(val building: IndoorBuilding) : IndoorChangeEvent() + +/** + * Returns a [Flow] of [CameraEvent] items so that camera movements can be observed. Using this to + * observe camera events will set listeners and thus override existing listeners to + * [GoogleMap.setOnCameraIdleListener], [GoogleMap.setOnCameraMoveCanceledListener], + * [GoogleMap.setOnCameraMoveListener] and [GoogleMap.setOnCameraMoveStartedListener]. + */ +@Deprecated( + message = "Use cameraIdleEvents(), cameraMoveCanceledEvents(), cameraMoveEvents() or cameraMoveStartedEvents", +) +public fun GoogleMap.cameraEvents(): Flow = + callbackFlow { + setOnCameraIdleListener { + trySend(CameraIdleEvent) + } + setOnCameraMoveCanceledListener { + trySend(CameraMoveCanceledEvent) + } + setOnCameraMoveListener { + trySend(CameraMoveEvent) + } + setOnCameraMoveStartedListener { + trySend(CameraMoveStartedEvent(it)) + } + awaitClose { + setOnCameraIdleListener(null) + setOnCameraMoveCanceledListener(null) + setOnCameraMoveListener(null) + setOnCameraMoveStartedListener(null) + } + } + +/** + * A suspending function that awaits the completion of the [cameraUpdate] animation. + * + * @param cameraUpdate the [CameraUpdate] to apply on the map + * @param durationMs the duration in milliseconds of the animation. Defaults to 3 seconds. + */ +public suspend inline fun GoogleMap.awaitAnimateCamera( + cameraUpdate: CameraUpdate, + durationMs: Int = 3000 +): Unit = + suspendCancellableCoroutine { continuation -> + animateCamera(cameraUpdate, durationMs, object : GoogleMap.CancelableCallback { + override fun onFinish() { + continuation.resume(Unit) + } + + override fun onCancel() { + continuation.cancel() + } + }) + } + +/** + * A suspending function that awaits for the map to be loaded. Uses + * [GoogleMap.setOnMapLoadedCallback]. + */ +public suspend inline fun GoogleMap.awaitMapLoad(): Unit = + suspendCoroutine { continuation -> + setOnMapLoadedCallback { + continuation.resume(Unit) + } + } + +/** + * Returns a flow that emits when the camera is idle. Using this to observe camera idle events will + * override an existing listener (if any) to [GoogleMap.setOnCameraIdleListener]. + */ +public fun GoogleMap.cameraIdleEvents(): Flow = + callbackFlow { + setOnCameraIdleListener { + trySend(Unit) + } + awaitClose { + setOnCameraIdleListener(null) + } + } + +/** + * Returns a flow that emits when a camera move is canceled. Using this to observe camera move + * cancel events will override an existing listener (if any) to + * [GoogleMap.setOnCameraMoveCanceledListener]. + */ +public fun GoogleMap.cameraMoveCanceledEvents(): Flow = + callbackFlow { + setOnCameraMoveCanceledListener { + trySend(Unit) + } + awaitClose { + setOnCameraMoveCanceledListener(null) + } + } + +/** + * Returns a flow that emits when the camera moves. Using this to observe camera move events will + * override an existing listener (if any) to [GoogleMap.setOnCameraMoveListener]. + */ +public fun GoogleMap.cameraMoveEvents(): Flow = + callbackFlow { + setOnCameraMoveListener { + trySend(Unit) + } + awaitClose { + setOnCameraMoveListener(null) + } + } + +/** + * A suspending function that returns a bitmap snapshot of the current view of the map. Uses + * [GoogleMap.snapshot]. + * + * @param bitmap an optional preallocated bitmap + * @return the snapshot + */ +public suspend inline fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = + suspendCoroutine { continuation -> + snapshot({ continuation.resume(it) }, bitmap) + } + +/** + * Returns a flow that emits when a camera move started. Using this to observe camera move start + * events will override an existing listener (if any) to [GoogleMap.setOnCameraMoveStartedListener]. + */ +public fun GoogleMap.cameraMoveStartedEvents(): Flow = + callbackFlow { + setOnCameraMoveStartedListener { + trySend(it) + } + awaitClose { + setOnCameraMoveStartedListener(null) + } + } + +/** + * Returns a flow that emits when a circle is clicked. Using this to observe circle clicks events + * will override an existing listener (if any) to [GoogleMap.setOnCircleClickListener]. + */ +public fun GoogleMap.circleClickEvents(): Flow = + callbackFlow { + setOnCircleClickListener { + trySend(it) + } + awaitClose { + setOnCircleClickListener(null) + } + } + +/** + * Returns a flow that emits when a ground overlay is clicked. Using this to observe ground overlay + * clicks events will override an existing listener (if any) to + * [GoogleMap.setOnGroundOverlayClickListener]. + */ +public fun GoogleMap.groundOverlayClicks(): Flow = + callbackFlow { + setOnGroundOverlayClickListener { + trySend(it) + } + awaitClose { + setOnGroundOverlayClickListener(null) + } + } + +/** + * Returns a flow that emits when the indoor state changes. Using this to observe indoor state + * change events will override an existing listener (if any) to + * [GoogleMap.setOnIndoorStateChangeListener] + */ +public fun GoogleMap.indoorStateChangeEvents(): Flow = + callbackFlow { + setOnIndoorStateChangeListener(object : GoogleMap.OnIndoorStateChangeListener { + override fun onIndoorBuildingFocused() { + trySend(IndoorBuildingFocusedEvent) + } + + override fun onIndoorLevelActivated(indoorBuilding: IndoorBuilding) { + trySend(IndoorLevelActivatedEvent(building = indoorBuilding)) + } + }) + awaitClose { + setOnIndoorStateChangeListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window is clicked. Using this to observe info + * info window clicks will override an existing listener (if any) to + * [GoogleMap.setOnInfoWindowClickListener] + */ +public fun GoogleMap.infoWindowClickEvents(): Flow = + callbackFlow { + setOnInfoWindowClickListener { + trySend(it) + } + awaitClose { + setOnInfoWindowClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window is closed. Using this to observe info + * window closes will override an existing listener (if any) to + * [GoogleMap.setOnInfoWindowCloseListener] + */ +public fun GoogleMap.infoWindowCloseEvents(): Flow = + callbackFlow { + setOnInfoWindowCloseListener { + trySend(it) + } + awaitClose { + setOnInfoWindowCloseListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window is long pressed. Using this to observe info + * window long presses will override an existing listener (if any) to + * [GoogleMap.setOnInfoWindowLongClickListener] + */ +public fun GoogleMap.infoWindowLongClickEvents(): Flow = + callbackFlow { + setOnInfoWindowLongClickListener { + trySend(it) + } + awaitClose { + setOnInfoWindowLongClickListener(null) + } + } + +/** + * Returns a flow that emits when the map is clicked. Using this to observe map click events will + * override an existing listener (if any) to [GoogleMap.setOnMapClickListener] + */ +public fun GoogleMap.mapClickEvents(): Flow = + callbackFlow { + setOnMapClickListener { + trySend(it) + } + awaitClose { + setOnMapClickListener(null) + } + } + +/** + * Returns a flow that emits when the map is long clicked. Using this to observe map click events + * will override an existing listener (if any) to [GoogleMap.setOnMapLongClickListener] + */ +public fun GoogleMap.mapLongClickEvents(): Flow = + callbackFlow { + setOnMapLongClickListener { + trySend(it) + } + awaitClose { + setOnMapLongClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker on the map is clicked. Using this to observe marker click + * events will override an existing listener (if any) to [GoogleMap.setOnMarkerClickListener] + */ +public fun GoogleMap.markerClickEvents(): Flow = + callbackFlow { + setOnMarkerClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnMarkerClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker is dragged. Using this to observer marker drag events + * will override existing listeners (if any) to [GoogleMap.setOnMarkerDragListener] + */ +public fun GoogleMap.markerDragEvents(): Flow = + callbackFlow { + setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + override fun onMarkerDragStart(marker: Marker) { + trySend(MarkerDragStartEvent(marker = marker)) + } + + override fun onMarkerDrag(marker: Marker) { + trySend(MarkerDragEvent(marker = marker)) + } + + override fun onMarkerDragEnd(marker: Marker) { + trySend(MarkerDragEndEvent(marker = marker)) + } + + }) + awaitClose { + setOnMarkerDragListener(null) + } + } + +/** + * Returns a flow that emits when the my location button is clicked. Using this to observe my + * location button click events will override an existing listener (if any) to + * [GoogleMap.setOnMyLocationButtonClickListener] + */ +public fun GoogleMap.myLocationButtonClickEvents(): Flow = + callbackFlow { + setOnMyLocationButtonClickListener { + trySend(Unit).isSuccess + } + awaitClose { + setOnMyLocationButtonClickListener(null) + } + } + +/** + * Returns a flow that emits when the my location blue dot is clicked. Using this to observe my + * location blue dot click events will override an existing listener (if any) to + * [GoogleMap.setOnMyLocationClickListener] + */ +public fun GoogleMap.myLocationClickEvents(): Flow = + callbackFlow { + setOnMyLocationClickListener { + trySend(it) + } + awaitClose { + setOnMyLocationClickListener(null) + } + } + +/** + * Returns a flow that emits when a PointOfInterest is clicked. Using this to observe + * PointOfInterest click events will override an existing listener (if any) to + * [GoogleMap.setOnPoiClickListener] + */ +public fun GoogleMap.poiClickEvents(): Flow = + callbackFlow { + setOnPoiClickListener { + trySend(it) + } + awaitClose { + setOnPoiClickListener(null) + } + } + +/** + * Returns a flow that emits when a Polygon is clicked. Using this to observe Polygon click events + * will override an existing listener (if any) to [GoogleMap.setOnPolygonClickListener] + */ +public fun GoogleMap.polygonClickEvents(): Flow = + callbackFlow { + setOnPolygonClickListener { + trySend(it) + } + awaitClose { + setOnPolygonClickListener(null) + } + } + +/** + * Returns a flow that emits when a Polyline is clicked. Using this to observe Polyline click events + * will override an existing listener (if any) to [GoogleMap.setOnPolylineClickListener] + */ +public fun GoogleMap.polylineClickEvents(): Flow = + callbackFlow { + setOnPolylineClickListener { + trySend(it) + } + awaitClose { + setOnPolylineClickListener(null) + } + } + +/** + * Builds a new [GoogleMapOptions] using the provided [optionsActions]. + * + * @return the constructed [GoogleMapOptions] + */ +public inline fun buildGoogleMapOptions(optionsActions: GoogleMapOptions.() -> Unit): GoogleMapOptions = + GoogleMapOptions().apply( + optionsActions + ) + +/** + * Adds a [Circle] to this [GoogleMap] using the function literal with receiver [optionsActions]. + * + * @return the added [Circle] + */ +public inline fun GoogleMap.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = + this.addCircle( + circleOptions(optionsActions) + ) + +/** + * Adds a [GroundOverlay] to this [GoogleMap] using the function literal with receiver + * [optionsActions]. + * + * @return the added [Circle] + */ +public inline fun GoogleMap.addGroundOverlay(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlay? = + this.addGroundOverlay( + groundOverlayOptions(optionsActions) + ) + +/** + * Adds a [Marker] to this [GoogleMap] using the function literal with receiver [optionsActions]. + * + * @return the added [Marker] + */ +public inline fun GoogleMap.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker? = + this.addMarker( + markerOptions(optionsActions) + ) + +/** + * Adds a [Polygon] to this [GoogleMap] using the function literal with receiver [optionsActions]. + * + * @return the added [Polygon] + */ +public inline fun GoogleMap.addPolygon(optionsActions: PolygonOptions.() -> Unit): Polygon = + this.addPolygon( + polygonOptions(optionsActions) + ) + +/** + * Adds a [Polyline] to this [GoogleMap] using the function literal with receiver [optionsActions]. + * + * @return the added [Polyline] + */ +public inline fun GoogleMap.addPolyline(optionsActions: PolylineOptions.() -> Unit): Polyline = + this.addPolyline( + polylineOptions(optionsActions) + ) + +/** + * Adds a [TileOverlay] to this [GoogleMap] using the function literal with receiver + * [optionsActions]. + * + * @return the added [Polyline] + */ +public inline fun GoogleMap.addTileOverlay(optionsActions: TileOverlayOptions.() -> Unit): TileOverlay? = + this.addTileOverlay( + tileOverlayOptions(optionsActions) + ) diff --git a/library/src/main/java/com/google/maps/android/LatLng.kt b/library/src/main/java/com/google/maps/android/LatLng.kt new file mode 100644 index 000000000..13860b67a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/LatLng.kt @@ -0,0 +1,215 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("NOTHING_TO_INLINE") + +package com.google.maps.android + +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.PolyUtil +import com.google.maps.android.SphericalUtil + +/** + * Returns the [LatLng.latitude] of this [LatLng]. + * + * e.g. + * ``` + * val (lat, _) = latLng + * ``` + */ +public inline operator fun LatLng.component1(): Double = this.latitude + +/** + * Returns the [LatLng.longitude] of this [LatLng]. + * + * e.g. + * ``` + * val (_, lng) = latLng + * ``` + */ +public inline operator fun LatLng.component2(): Double = this.longitude + +/** + * Computes whether the given [latLng] lies on or is near this polyline within [tolerance] (in + * meters). + * + * @param latLng the LatLng to inspect + * @param geodesic if this polyline is geodesic or not + * @param tolerance the tolerance in meters + * @return true if [latLng] is on this path, otherwise, false + * + * @see PolyUtil.isLocationOnPath + */ +public inline fun List.isLocationOnPath( + latLng: LatLng, + geodesic: Boolean, + tolerance: Double = 0.1 +): Boolean = PolyUtil.isLocationOnPath(latLng, this, geodesic, tolerance) + +/** + * Checks whether or not [latLng] lies on or is near the edge of this polygon within the [tolerance] + * (in meters). The default value is [PolyUtil.DEFAULT_TOLERANCE]. + * + * @param latLng the LatLng to inspect + * @param geodesic if this polygon is geodesic or not + * @param tolerance the tolerance in meters + * @return true if [latLng] lies on or is near the edge of this Polygon, otherwise, false + * + * @see PolyUtil.isLocationOnEdge + */ +public inline fun List.isOnEdge( + latLng: LatLng, + geodesic: Boolean, + tolerance: Double = 0.1 +): Boolean = PolyUtil.isLocationOnEdge(latLng, this, geodesic, tolerance) + +/** + * Computes whether the [latLng] lies inside this. + * + * The polygon is always considered closed, regardless of whether the last point equals + * the first or not. + * + * Inside is defined as not containing the South Pole -- the South Pole is always outside. + * The polygon is formed of great circle segments if [geodesic] is true, and of rhumb + * (loxodromic) segments otherwise. + * + * @param latLng the LatLng to check if it is contained within this polygon + * @param geodesic if this Polygon is geodesic or not + * + * @see PolyUtil.containsLocation + */ +public inline fun List.containsLocation(latLng: LatLng, geodesic: Boolean): Boolean = + PolyUtil.containsLocation(latLng, this, geodesic) + +/** + * Simplifies this list of LatLng using the Douglas-Peucker decimation. Increasing the value of + * [tolerance] will result in fewer points. + * + * @param tolerance the tolerance in meters + * @return the simplified list of [LatLng] + * + * @see PolyUtil.simplify + */ +public inline fun List.simplify(tolerance: Double): List = + PolyUtil.simplify(this, tolerance) + +/** + * Decodes this encoded string into a [LatLng] list. + * + * @return the decoded [LatLng] list + * + * @see [Polyline Algorithm Format](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) + */ +public inline fun String.toLatLngList(): List = PolyUtil.decode(this) + +/** + * Encodes this [LatLng] list in a String using the + * [Polyline Algorithm Format](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). + * + * @return the encoded String + * + * @see [Polyline Algorithm Format](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) + */ +public inline fun List.latLngListEncode(): String = PolyUtil.encode(this) + +/** + * Checks whether or not this [LatLng] list is a closed Polygon. + * + * @return true if this list is a closed Polygon, otherwise, false + * + * @see PolyUtil.isClosedPolygon + */ +public inline fun List.isClosedPolygon(): Boolean = PolyUtil.isClosedPolygon(this) + +/** + * Computes the length of this path on Earth. + * + * @return the length of this path in meters + */ +public inline fun List.sphericalPathLength(): Double = SphericalUtil.computeLength(this) + +/** + * Computes the area under a closed path on Earth. + * + * @return the area in square meters + */ +public inline fun List.sphericalPolygonArea(): Double = SphericalUtil.computeArea(this) + +/** + * Computes the signed area under a closed path on Earth. The sign of the area may be used to + * determine the orientation of the path. + * + * @return the signed area in square meters + */ +public inline fun List.sphericalPolygonSignedArea(): Double = SphericalUtil.computeSignedArea(this) + +/** + * Computes the heading from this LatLng to [toLatLng]. + * + * @param toLatLng the other LatLng to compute the heading to + * @return the heading expressed in degrees clockwise from North within the range [-180, 180] + * + * @see SphericalUtil.computeHeading + */ +public inline fun LatLng.sphericalHeading(toLatLng: LatLng): Double = + SphericalUtil.computeHeading(this, toLatLng) + +/** + * Offsets this LatLng from the provided [distance] and [heading] and returns the result. + * + * @param distance the distance to offset by in meters + * @param heading the heading to offset by in degrees clockwise from north + * @return the resulting LatLng + * + * @see SphericalUtil.computeOffset + */ +public inline fun LatLng.withSphericalOffset(distance: Double, heading: Double): LatLng = + SphericalUtil.computeOffset(this, distance, heading) + +/** + * Attempts to compute the origin [LatLng] from this LatLng where [distance] meters have been + * traveled with heading value [heading]. + * + * @param distance the distance traveled from origin in meters + * @param heading the heading from origin to this LatLng (measured in degrees clockwise from North) + * @return the computed origin if a solution is available, otherwise, null + * + * @see SphericalUtil.computeOffsetOrigin + */ +public inline fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: Double): LatLng? = + SphericalUtil.computeOffsetOrigin(this, distance, heading) + +/** + * Returns an interpolated [LatLng] between this LatLng and [to] by the provided fractional value + * [fraction]. + * + * @param to the destination LatLng + * @param fraction the fraction to interpolate by where the range is [0.0, 1.0] + * @return the interpolated [LatLng] + * + * @see [Slerp](http://en.wikipedia.org/wiki/Slerp) + */ +public inline fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: Double): LatLng = + SphericalUtil.interpolate(this, to, fraction) + +/** + * Computes the spherical distance between this LatLng and [to]. + * + * @param to the LatLng to compute the distance to + * @return the distance between this and [to] in meters + */ +public inline fun LatLng.sphericalDistance(to: LatLng): Double = + SphericalUtil.computeDistanceBetween(this, to) diff --git a/library/src/main/java/com/google/maps/android/MapFragment.kt b/library/src/main/java/com/google/maps/android/MapFragment.kt new file mode 100644 index 000000000..b8f634a6a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/MapFragment.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapFragment +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + + +/** + * A suspending function that provides an instance of a [GoogleMap] from this [MapFragment]. + * This is an alternative to [MapFragment.getMapAsync] by using coroutines to obtain a [GoogleMap]. + * + * @return the [GoogleMap] instance + */ +public suspend inline fun MapFragment.awaitMap(): GoogleMap = + suspendCancellableCoroutine { continuation -> + getMapAsync { + continuation.resume(it) + } + } diff --git a/library/src/main/java/com/google/maps/android/MapView.kt b/library/src/main/java/com/google/maps/android/MapView.kt new file mode 100644 index 000000000..dacf2909b --- /dev/null +++ b/library/src/main/java/com/google/maps/android/MapView.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapView +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * A suspending function that provides an instance of [GoogleMap] from this [MapView]. This is + * an alternative to [MapView.getMapAsync] by using coroutines to obtain the [GoogleMap]. + * + * @return the [GoogleMap] instance + */ +public suspend inline fun MapView.awaitMap(): GoogleMap = + suspendCoroutine { continuation -> + getMapAsync { + continuation.resume(it) + } + } diff --git a/library/src/main/java/com/google/maps/android/MapsExperimentalFeature.kt b/library/src/main/java/com/google/maps/android/MapsExperimentalFeature.kt new file mode 100644 index 000000000..af6492605 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/MapsExperimentalFeature.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android + +/** + * Annotation for APIs that are experimental and require explicit opt-in annotation before use. + */ +@RequiresOptIn +@Retention(AnnotationRetention.BINARY) +public annotation class MapsExperimentalFeature diff --git a/library/src/main/java/com/google/maps/android/Polygon.kt b/library/src/main/java/com/google/maps/android/Polygon.kt new file mode 100644 index 000000000..95487f2b1 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/Polygon.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:Suppress("NOTHING_TO_INLINE") +package com.google.maps.android + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polygon +import com.google.maps.android.PolyUtil +import com.google.maps.android.SphericalUtil + +/** + * Computes whether or not [latLng] is contained within this Polygon. + * + * @param latLng the LatLng to inspect + * @return true if [latLng] is contained within this Polygon, otherwise, false + * + * @see PolyUtil.containsLocation + */ +public inline fun Polygon.contains(latLng: LatLng): Boolean = + PolyUtil.containsLocation(latLng, this.points, this.isGeodesic) + +/** + * Checks whether or not [latLng] lies on or is near the edge of this Polygon within a tolerance + * (in meters) of [tolerance]. The default value is [PolyUtil.DEFAULT_TOLERANCE]. + * + * @param latLng the LatLng to inspect + * @param tolerance the tolerance in meters + * @return true if [latLng] lies on or is near the edge of this Polygon, otherwise, false + * + * @see PolyUtil.isLocationOnEdge + */ +public inline fun Polygon.isOnEdge(latLng: LatLng, tolerance: Double = 0.1): Boolean = + PolyUtil.isLocationOnEdge(latLng, this.points, this.isGeodesic, tolerance) + +/** + * The area of this Polygon on Earth in square meters. + */ +public inline val Polygon.area: Double + get() = SphericalUtil.computeArea(this.points) + +/** + * Computes the signed area under a closed path on Earth. The sign of the area may be used to + * determine the orientation of the path. + */ +public inline val Polygon.signedArea: Double + get() = SphericalUtil.computeSignedArea(this.points) diff --git a/library/src/main/java/com/google/maps/android/Polyline.kt b/library/src/main/java/com/google/maps/android/Polyline.kt new file mode 100644 index 000000000..de545a0dc --- /dev/null +++ b/library/src/main/java/com/google/maps/android/Polyline.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:Suppress("NOTHING_TO_INLINE") +package com.google.maps.android + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polyline +import com.google.maps.android.PolyUtil +import com.google.maps.android.SphericalUtil + +/** + * Computes where the given [latLng] is contained on or near this Polyline within a specified + * tolerance in meters. + */ +public inline fun Polyline.contains(latLng: LatLng, tolerance: Double = 0.1): Boolean = + PolyUtil.isLocationOnPath(latLng, this.points, this.isGeodesic, tolerance) + +/** + * The spherical length of this Polyline on Earth as measured in meters. + */ +public inline val Polyline.sphericalPathLength: Double + get() = SphericalUtil.computeLength(this.points) diff --git a/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt new file mode 100644 index 000000000..58ef5754d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.StreetViewPanoramaFragment +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * A suspending function that provides an instance of a [StreetViewPanorama] from this + * [StreetViewPanoramaFragment]. This is an alternative to using + * [StreetViewPanoramaFragment.getStreetViewPanoramaAsync] by using coroutines to obtain a + * [StreetViewPanorama]. + * + * @return the [StreetViewPanorama] + */ +public suspend inline fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCoroutine { continuation -> + getStreetViewPanoramaAsync { + continuation.resume(it) + } + } \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt new file mode 100644 index 000000000..0f79a0145 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android + +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.StreetViewPanoramaView +import com.google.android.gms.maps.model.StreetViewPanoramaCamera +import com.google.android.gms.maps.model.StreetViewPanoramaLocation +import com.google.android.gms.maps.model.StreetViewPanoramaOrientation +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * A suspending function that provides an instance of a [StreetViewPanorama] from this + * [StreetViewPanoramaView]. This is an alternative to using + * [StreetViewPanoramaView.getStreetViewPanoramaAsync] by using coroutines to obtain a + * [StreetViewPanorama]. + * + * @return the [StreetViewPanorama] instance + */ +public suspend inline fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCoroutine { continuation -> + getStreetViewPanoramaAsync { + continuation.resume(it) + } + } + +/** + * Returns a flow that emits when the street view panorama camera changes. Using this to + * observe panorama camera change events will override an existing listener (if any) to + * [StreetViewPanorama.setOnStreetViewPanoramaCameraChangeListener]. + */ +public fun StreetViewPanorama.cameraChangeEvents(): Flow = + callbackFlow { + setOnStreetViewPanoramaCameraChangeListener { + trySend(it) + } + awaitClose { + setOnStreetViewPanoramaCameraChangeListener(null) + } + } + +/** + * Returns a flow that emits when the street view panorama loads a new panorama. Using this to + * observe panorama load change events will override an existing listener (if any) to + * [StreetViewPanorama.setOnStreetViewPanoramaChangeListener]. + */ +public fun StreetViewPanorama.changeEvents(): Flow = + callbackFlow { + setOnStreetViewPanoramaChangeListener { + trySend(it) + } + awaitClose { + setOnStreetViewPanoramaChangeListener(null) + } + } + +/** + * Returns a flow that emits when the street view panorama is clicked. Using this to + * observe panorama click events will override an existing listener (if any) to + * [StreetViewPanorama.setOnStreetViewPanoramaClickListener]. + */ +public fun StreetViewPanorama.clickEvents(): Flow = + callbackFlow { + setOnStreetViewPanoramaClickListener { + trySend(it) + } + awaitClose { + setOnStreetViewPanoramaClickListener(null) + } + } + +/** + * Returns a flow that emits when the street view panorama is long clicked. Using this to + * observe panorama long click events will override an existing listener (if any) to + * [StreetViewPanorama.setOnStreetViewPanoramaLongClickListener]. + */ +public fun StreetViewPanorama.longClickEvents(): Flow = + callbackFlow { + setOnStreetViewPanoramaLongClickListener { + trySend(it) + } + awaitClose { + setOnStreetViewPanoramaLongClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/SupportMapFragment.kt b/library/src/main/java/com/google/maps/android/SupportMapFragment.kt new file mode 100644 index 000000000..0865d01e9 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/SupportMapFragment.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.SupportMapFragment +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +/** + * A suspending function that provides an instance of a [GoogleMap] from this [SupportMapFragment]. + * This is an alternative to using [SupportMapFragment.getMapAsync] by using coroutines to obtain + * a [GoogleMap]. + * + * @return the [GoogleMap] instance + */ +public suspend inline fun SupportMapFragment.awaitMap(): GoogleMap = + suspendCancellableCoroutine { continuation -> + getMapAsync { + continuation.resume(it) + } + } diff --git a/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt new file mode 100644 index 000000000..1f29844e7 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * A suspending function that provides an instance of a [StreetViewPanorama] from this + * [SupportStreetViewPanoramaFragment]. This is an alternative to using + * [SupportStreetViewPanoramaFragment.getStreetViewPanoramaAsync] by using coroutines to obtain a + * [StreetViewPanorama]. + * + * @return the [StreetViewPanorama] + */ +public suspend inline fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCoroutine { continuation -> + getStreetViewPanoramaAsync { + continuation.resume(it) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/CircleManager.kt b/library/src/main/java/com/google/maps/android/collections/CircleManager.kt index 519edaf1c..7aa82da3e 100644 --- a/library/src/main/java/com/google/maps/android/collections/CircleManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/CircleManager.kt @@ -18,6 +18,9 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Circle import com.google.android.gms.maps.model.CircleOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -69,3 +72,31 @@ open class CircleManager(map: GoogleMap) : } } } + +/** + * Adds a new [Circle] to the underlying map and to this [CircleManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun CircleManager.Collection.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = + this.addCircle( + CircleOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a circle in this collection is clicked. Using this to observe circle clicks + * will override an existing listener (if any) to [CircleManager.Collection.setOnCircleClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun CircleManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnCircleClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnCircleClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt b/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt index 7dd9d07d1..eaeead850 100644 --- a/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt @@ -18,6 +18,9 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.GroundOverlayOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -72,3 +75,33 @@ open class GroundOverlayManager(map: GoogleMap) : } } } + +/** + * Adds a new [GroundOverlay] to the underlying map and to this [GroundOverlayManager.Collection] + * with the provided [optionsActions]. + */ +public inline fun GroundOverlayManager.Collection.addGroundOverlay( + optionsActions: GroundOverlayOptions.() -> Unit +): GroundOverlay = + this.addGroundOverlay( + GroundOverlayOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a ground overlay in this collection is clicked. Using this to observe ground overlay clicks + * will override an existing listener (if any) to [GroundOverlayManager.Collection.setOnGroundOverlayClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun GroundOverlayManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnGroundOverlayClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnGroundOverlayClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt b/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt index ebb2b873c..478af0013 100644 --- a/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt @@ -20,6 +20,9 @@ import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.AdvancedMarkerOptions import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.MarkerOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -127,3 +130,69 @@ open class MarkerManager(map: GoogleMap) : } } } + +/** + * Adds a new [Marker] to the underlying map and to this [MarkerManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun MarkerManager.Collection.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker = + this.addMarker( + MarkerOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a marker in this collection is clicked. Using this to observe marker clicks + * will override an existing listener (if any) to [MarkerManager.Collection.setOnMarkerClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun MarkerManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnMarkerClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnMarkerClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window in this collection is clicked. Using this to observe info window clicks + * will override an existing listener (if any) to [MarkerManager.Collection.setOnInfoWindowClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun MarkerManager.Collection.infoWindowClickEvents(): Flow = + callbackFlow { + setOnInfoWindowClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnInfoWindowClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window in this collection is long clicked. Using this to observe info window long clicks + * will override an existing listener (if any) to [MarkerManager.Collection.setOnInfoWindowLongClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun MarkerManager.Collection.infoWindowLongClickEvents(): Flow = + callbackFlow { + setOnInfoWindowLongClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnInfoWindowLongClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt b/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt index 98eef696a..83dc489d4 100644 --- a/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt @@ -18,6 +18,9 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Polygon import com.google.android.gms.maps.model.PolygonOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -69,3 +72,33 @@ open class PolygonManager(map: GoogleMap) : } } } + +/** + * Adds a new [Polygon] to the underlying map and to this [PolygonManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun PolygonManager.Collection.addPolygon( + optionsActions: PolygonOptions.() -> Unit +): Polygon = + this.addPolygon( + PolygonOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a polygon in this collection is clicked. Using this to observe polygon clicks + * will override an existing listener (if any) to [PolygonManager.Collection.setOnPolygonClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun PolygonManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnPolygonClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnPolygonClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt b/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt index 61de4b7ef..55f305b55 100644 --- a/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt @@ -18,6 +18,9 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Polyline import com.google.android.gms.maps.model.PolylineOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -71,3 +74,33 @@ open class PolylineManager(map: GoogleMap) : } } } + +/** + * Adds a new [Polyline] to the underlying map and to this [PolylineManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun PolylineManager.Collection.addPolyline( + optionsActions: PolylineOptions.() -> Unit +): Polyline = + this.addPolyline( + PolylineOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a polyline in this collection is clicked. Using this to observe polyline clicks + * will override an existing listener (if any) to [PolylineManager.Collection.setOnPolylineClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun PolylineManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnPolylineClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnPolylineClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt new file mode 100644 index 000000000..564cc577f --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt @@ -0,0 +1,200 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import android.graphics.Bitmap +import android.location.Location +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.android.gms.maps.model.IndoorBuilding +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PointOfInterest +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.TileOverlay +import com.google.android.gms.maps.model.TileOverlayOptions +import kotlinx.coroutines.flow.Flow + +import com.google.maps.android.cameraEvents as canonicalCameraEvents +import com.google.maps.android.awaitAnimateCamera as canonicalAwaitAnimateCamera +import com.google.maps.android.awaitMapLoad as canonicalAwaitMapLoad +import com.google.maps.android.cameraIdleEvents as canonicalCameraIdleEvents +import com.google.maps.android.cameraMoveCanceledEvents as canonicalCameraMoveCanceledEvents +import com.google.maps.android.cameraMoveEvents as canonicalCameraMoveEvents +import com.google.maps.android.awaitSnapshot as canonicalAwaitSnapshot +import com.google.maps.android.cameraMoveStartedEvents as canonicalCameraMoveStartedEvents +import com.google.maps.android.circleClickEvents as canonicalCircleClickEvents +import com.google.maps.android.groundOverlayClicks as canonicalGroundOverlayClicks +import com.google.maps.android.indoorStateChangeEvents as canonicalIndoorStateChangeEvents +import com.google.maps.android.infoWindowClickEvents as canonicalInfoWindowClickEvents +import com.google.maps.android.infoWindowCloseEvents as canonicalInfoWindowCloseEvents +import com.google.maps.android.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents +import com.google.maps.android.mapClickEvents as canonicalMapClickEvents +import com.google.maps.android.mapLongClickEvents as canonicalMapLongClickEvents +import com.google.maps.android.markerClickEvents as canonicalMarkerClickEvents +import com.google.maps.android.markerDragEvents as canonicalMarkerDragEvents +import com.google.maps.android.myLocationButtonClickEvents as canonicalMyLocationButtonClickEvents +import com.google.maps.android.myLocationClickEvents as canonicalMyLocationClickEvents +import com.google.maps.android.poiClickEvents as canonicalPoiClickEvents +import com.google.maps.android.polygonClickEvents as canonicalPolygonClickEvents +import com.google.maps.android.polylineClickEvents as canonicalPolylineClickEvents +import com.google.maps.android.buildGoogleMapOptions as canonicalBuildGoogleMapOptions +import com.google.maps.android.addCircle as canonicalAddCircle +import com.google.maps.android.addGroundOverlay as canonicalAddGroundOverlay +import com.google.maps.android.addMarker as canonicalAddMarker +import com.google.maps.android.addPolygon as canonicalAddPolygon +import com.google.maps.android.addPolyline as canonicalAddPolyline +import com.google.maps.android.addTileOverlay as canonicalAddTileOverlay + +@Deprecated("Moved to com.google.maps.android.MoveStartedReason", ReplaceWith("MoveStartedReason", "com.google.maps.android.MoveStartedReason")) +public typealias MoveStartedReason = com.google.maps.android.MoveStartedReason + +@Deprecated("Moved to com.google.maps.android.CameraEvent", ReplaceWith("CameraEvent", "com.google.maps.android.CameraEvent")) +public typealias CameraEvent = com.google.maps.android.CameraEvent + +@Deprecated("Moved to com.google.maps.android.CameraIdleEvent", ReplaceWith("CameraIdleEvent", "com.google.maps.android.CameraIdleEvent")) +public typealias CameraIdleEvent = com.google.maps.android.CameraIdleEvent + +@Deprecated("Moved to com.google.maps.android.CameraMoveCanceledEvent", ReplaceWith("CameraMoveCanceledEvent", "com.google.maps.android.CameraMoveCanceledEvent")) +public typealias CameraMoveCanceledEvent = com.google.maps.android.CameraMoveCanceledEvent + +@Deprecated("Moved to com.google.maps.android.CameraMoveEvent", ReplaceWith("CameraMoveEvent", "com.google.maps.android.CameraMoveEvent")) +public typealias CameraMoveEvent = com.google.maps.android.CameraMoveEvent + +@Deprecated("Moved to com.google.maps.android.CameraMoveStartedEvent", ReplaceWith("CameraMoveStartedEvent", "com.google.maps.android.CameraMoveStartedEvent")) +public typealias CameraMoveStartedEvent = com.google.maps.android.CameraMoveStartedEvent + +@Deprecated("Moved to com.google.maps.android.OnMarkerDragEvent", ReplaceWith("OnMarkerDragEvent", "com.google.maps.android.OnMarkerDragEvent")) +public typealias OnMarkerDragEvent = com.google.maps.android.OnMarkerDragEvent + +@Deprecated("Moved to com.google.maps.android.MarkerDragEvent", ReplaceWith("MarkerDragEvent", "com.google.maps.android.MarkerDragEvent")) +public typealias MarkerDragEvent = com.google.maps.android.MarkerDragEvent + +@Deprecated("Moved to com.google.maps.android.MarkerDragEndEvent", ReplaceWith("MarkerDragEndEvent", "com.google.maps.android.MarkerDragEndEvent")) +public typealias MarkerDragEndEvent = com.google.maps.android.MarkerDragEndEvent + +@Deprecated("Moved to com.google.maps.android.MarkerDragStartEvent", ReplaceWith("MarkerDragStartEvent", "com.google.maps.android.MarkerDragStartEvent")) +public typealias MarkerDragStartEvent = com.google.maps.android.MarkerDragStartEvent + +@Deprecated("Moved to com.google.maps.android.IndoorChangeEvent", ReplaceWith("IndoorChangeEvent", "com.google.maps.android.IndoorChangeEvent")) +public typealias IndoorChangeEvent = com.google.maps.android.IndoorChangeEvent + +@Deprecated("Moved to com.google.maps.android.IndoorBuildingFocusedEvent", ReplaceWith("IndoorBuildingFocusedEvent", "com.google.maps.android.IndoorBuildingFocusedEvent")) +public typealias IndoorBuildingFocusedEvent = com.google.maps.android.IndoorBuildingFocusedEvent + +@Deprecated("Moved to com.google.maps.android.IndoorLevelActivatedEvent", ReplaceWith("IndoorLevelActivatedEvent", "com.google.maps.android.IndoorLevelActivatedEvent")) +public typealias IndoorLevelActivatedEvent = com.google.maps.android.IndoorLevelActivatedEvent + +@Suppress("DEPRECATION") +@Deprecated("Use cameraIdleEvents(), cameraMoveCanceledEvents(), cameraMoveEvents() or cameraMoveStartedEvents") +public fun GoogleMap.cameraEvents(): Flow = this.canonicalCameraEvents() + +@Deprecated("Moved to com.google.maps.android.awaitAnimateCamera", ReplaceWith("awaitAnimateCamera(cameraUpdate, durationMs)", "com.google.maps.android.awaitAnimateCamera")) +public suspend inline fun GoogleMap.awaitAnimateCamera(cameraUpdate: CameraUpdate, durationMs: Int = 3000): Unit = this.canonicalAwaitAnimateCamera(cameraUpdate, durationMs) + +@Deprecated("Moved to com.google.maps.android.awaitMapLoad", ReplaceWith("awaitMapLoad()", "com.google.maps.android.awaitMapLoad")) +public suspend inline fun GoogleMap.awaitMapLoad(): Unit = this.canonicalAwaitMapLoad() + +@Deprecated("Moved to com.google.maps.android.cameraIdleEvents", ReplaceWith("cameraIdleEvents()", "com.google.maps.android.cameraIdleEvents")) +public fun GoogleMap.cameraIdleEvents(): Flow = this.canonicalCameraIdleEvents() + +@Deprecated("Moved to com.google.maps.android.cameraMoveCanceledEvents", ReplaceWith("cameraMoveCanceledEvents()", "com.google.maps.android.cameraMoveCanceledEvents")) +public fun GoogleMap.cameraMoveCanceledEvents(): Flow = this.canonicalCameraMoveCanceledEvents() + +@Deprecated("Moved to com.google.maps.android.cameraMoveEvents", ReplaceWith("cameraMoveEvents()", "com.google.maps.android.cameraMoveEvents")) +public fun GoogleMap.cameraMoveEvents(): Flow = this.canonicalCameraMoveEvents() + +@Deprecated("Moved to com.google.maps.android.awaitSnapshot", ReplaceWith("awaitSnapshot(bitmap)", "com.google.maps.android.awaitSnapshot")) +public suspend inline fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = this.canonicalAwaitSnapshot(bitmap) + +@Deprecated("Moved to com.google.maps.android.cameraMoveStartedEvents", ReplaceWith("cameraMoveStartedEvents()", "com.google.maps.android.cameraMoveStartedEvents")) +public fun GoogleMap.cameraMoveStartedEvents(): Flow = this.canonicalCameraMoveStartedEvents() + +@Deprecated("Moved to com.google.maps.android.circleClickEvents", ReplaceWith("circleClickEvents()", "com.google.maps.android.circleClickEvents")) +public fun GoogleMap.circleClickEvents(): Flow = this.canonicalCircleClickEvents() + +@Deprecated("Moved to com.google.maps.android.groundOverlayClicks", ReplaceWith("groundOverlayClicks()", "com.google.maps.android.groundOverlayClicks")) +public fun GoogleMap.groundOverlayClicks(): Flow = this.canonicalGroundOverlayClicks() + +@Deprecated("Moved to com.google.maps.android.indoorStateChangeEvents", ReplaceWith("indoorStateChangeEvents()", "com.google.maps.android.indoorStateChangeEvents")) +public fun GoogleMap.indoorStateChangeEvents(): Flow = this.canonicalIndoorStateChangeEvents() + +@Deprecated("Moved to com.google.maps.android.infoWindowClickEvents", ReplaceWith("infoWindowClickEvents()", "com.google.maps.android.infoWindowClickEvents")) +public fun GoogleMap.infoWindowClickEvents(): Flow = this.canonicalInfoWindowClickEvents() + +@Deprecated("Moved to com.google.maps.android.infoWindowCloseEvents", ReplaceWith("infoWindowCloseEvents()", "com.google.maps.android.infoWindowCloseEvents")) +public fun GoogleMap.infoWindowCloseEvents(): Flow = this.canonicalInfoWindowCloseEvents() + +@Deprecated("Moved to com.google.maps.android.infoWindowLongClickEvents", ReplaceWith("infoWindowLongClickEvents()", "com.google.maps.android.infoWindowLongClickEvents")) +public fun GoogleMap.infoWindowLongClickEvents(): Flow = this.canonicalInfoWindowLongClickEvents() + +@Deprecated("Moved to com.google.maps.android.mapClickEvents", ReplaceWith("mapClickEvents()", "com.google.maps.android.mapClickEvents")) +public fun GoogleMap.mapClickEvents(): Flow = this.canonicalMapClickEvents() + +@Deprecated("Moved to com.google.maps.android.mapLongClickEvents", ReplaceWith("mapLongClickEvents()", "com.google.maps.android.mapLongClickEvents")) +public fun GoogleMap.mapLongClickEvents(): Flow = this.canonicalMapLongClickEvents() + +@Deprecated("Moved to com.google.maps.android.markerClickEvents", ReplaceWith("markerClickEvents()", "com.google.maps.android.markerClickEvents")) +public fun GoogleMap.markerClickEvents(): Flow = this.canonicalMarkerClickEvents() + +@Deprecated("Moved to com.google.maps.android.markerDragEvents", ReplaceWith("markerDragEvents()", "com.google.maps.android.markerDragEvents")) +public fun GoogleMap.markerDragEvents(): Flow = this.canonicalMarkerDragEvents() + +@Deprecated("Moved to com.google.maps.android.myLocationButtonClickEvents", ReplaceWith("myLocationButtonClickEvents()", "com.google.maps.android.myLocationButtonClickEvents")) +public fun GoogleMap.myLocationButtonClickEvents(): Flow = this.canonicalMyLocationButtonClickEvents() + +@Deprecated("Moved to com.google.maps.android.myLocationClickEvents", ReplaceWith("myLocationClickEvents()", "com.google.maps.android.myLocationClickEvents")) +public fun GoogleMap.myLocationClickEvents(): Flow = this.canonicalMyLocationClickEvents() + +@Deprecated("Moved to com.google.maps.android.poiClickEvents", ReplaceWith("poiClickEvents()", "com.google.maps.android.poiClickEvents")) +public fun GoogleMap.poiClickEvents(): Flow = this.canonicalPoiClickEvents() + +@Deprecated("Moved to com.google.maps.android.polygonClickEvents", ReplaceWith("polygonClickEvents()", "com.google.maps.android.polygonClickEvents")) +public fun GoogleMap.polygonClickEvents(): Flow = this.canonicalPolygonClickEvents() + +@Deprecated("Moved to com.google.maps.android.polylineClickEvents", ReplaceWith("polylineClickEvents()", "com.google.maps.android.polylineClickEvents")) +public fun GoogleMap.polylineClickEvents(): Flow = this.canonicalPolylineClickEvents() + +@Deprecated("Moved to com.google.maps.android.buildGoogleMapOptions", ReplaceWith("buildGoogleMapOptions(optionsActions)", "com.google.maps.android.buildGoogleMapOptions")) +public inline fun buildGoogleMapOptions(optionsActions: GoogleMapOptions.() -> Unit): GoogleMapOptions = canonicalBuildGoogleMapOptions(optionsActions) + +@Deprecated("Moved to com.google.maps.android.addCircle", ReplaceWith("addCircle(optionsActions)", "com.google.maps.android.addCircle")) +public inline fun GoogleMap.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = this.canonicalAddCircle(optionsActions) + +@Deprecated("Moved to com.google.maps.android.addGroundOverlay", ReplaceWith("addGroundOverlay(optionsActions)", "com.google.maps.android.addGroundOverlay")) +public inline fun GoogleMap.addGroundOverlay(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlay? = this.canonicalAddGroundOverlay(optionsActions) + +@Deprecated("Moved to com.google.maps.android.addMarker", ReplaceWith("addMarker(optionsActions)", "com.google.maps.android.addMarker")) +public inline fun GoogleMap.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker? = this.canonicalAddMarker(optionsActions) + +@Deprecated("Moved to com.google.maps.android.addPolygon", ReplaceWith("addPolygon(optionsActions)", "com.google.maps.android.addPolygon")) +public inline fun GoogleMap.addPolygon(optionsActions: PolygonOptions.() -> Unit): Polygon = this.canonicalAddPolygon(optionsActions) + +@Deprecated("Moved to com.google.maps.android.addPolyline", ReplaceWith("addPolyline(optionsActions)", "com.google.maps.android.addPolyline")) +public inline fun GoogleMap.addPolyline(optionsActions: PolylineOptions.() -> Unit): Polyline = this.canonicalAddPolyline(optionsActions) + +@Deprecated("Moved to com.google.maps.android.addTileOverlay", ReplaceWith("addTileOverlay(optionsActions)", "com.google.maps.android.addTileOverlay")) +public inline fun GoogleMap.addTileOverlay(optionsActions: TileOverlayOptions.() -> Unit): TileOverlay? = this.canonicalAddTileOverlay(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt b/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt new file mode 100644 index 000000000..4dea2d039 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt @@ -0,0 +1,28 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapFragment +import com.google.maps.android.awaitMap as canonicalAwaitMap + +@Deprecated( + message = "Use com.google.maps.android.awaitMap instead", + replaceWith = ReplaceWith("awaitMap()", "com.google.maps.android.awaitMap"), + level = DeprecationLevel.WARNING +) +public suspend inline fun MapFragment.awaitMap(): GoogleMap = this.canonicalAwaitMap() diff --git a/library/src/main/java/com/google/maps/android/ktx/MapView.kt b/library/src/main/java/com/google/maps/android/ktx/MapView.kt new file mode 100644 index 000000000..3c407dad5 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapView.kt @@ -0,0 +1,28 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapView +import com.google.maps.android.awaitMap as canonicalAwaitMap + +@Deprecated( + message = "Use com.google.maps.android.awaitMap instead", + replaceWith = ReplaceWith("awaitMap()", "com.google.maps.android.awaitMap"), + level = DeprecationLevel.WARNING +) +public suspend inline fun MapView.awaitMap(): GoogleMap = this.canonicalAwaitMap() diff --git a/library/src/main/java/com/google/maps/android/ktx/MapsExperimentalFeature.kt b/library/src/main/java/com/google/maps/android/ktx/MapsExperimentalFeature.kt new file mode 100644 index 000000000..099426c7e --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapsExperimentalFeature.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +@RequiresOptIn +@Deprecated( + message = "The KTX library functionality has been moved to com.google.maps.android. Use com.google.maps.android.MapsExperimentalFeature instead.", + replaceWith = ReplaceWith("MapsExperimentalFeature", "com.google.maps.android.MapsExperimentalFeature"), + level = DeprecationLevel.WARNING +) +@Retention(AnnotationRetention.BINARY) +public annotation class MapsExperimentalFeature diff --git a/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt new file mode 100644 index 000000000..340f49f68 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt @@ -0,0 +1,28 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.StreetViewPanoramaFragment +import com.google.maps.android.awaitStreetViewPanorama as canonicalAwaitStreetViewPanorama + +@Deprecated( + message = "Use com.google.maps.android.awaitStreetViewPanorama instead", + replaceWith = ReplaceWith("awaitStreetViewPanorama()", "com.google.maps.android.awaitStreetViewPanorama"), + level = DeprecationLevel.WARNING +) +public suspend inline fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() diff --git a/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt new file mode 100644 index 000000000..77ad58644 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt @@ -0,0 +1,64 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.StreetViewPanoramaView +import com.google.android.gms.maps.model.StreetViewPanoramaCamera +import com.google.android.gms.maps.model.StreetViewPanoramaLocation +import com.google.android.gms.maps.model.StreetViewPanoramaOrientation +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.awaitStreetViewPanorama as canonicalAwaitStreetViewPanorama +import com.google.maps.android.cameraChangeEvents as canonicalCameraChangeEvents +import com.google.maps.android.changeEvents as canonicalChangeEvents +import com.google.maps.android.clickEvents as canonicalClickEvents +import com.google.maps.android.longClickEvents as canonicalLongClickEvents + +@Deprecated( + message = "Use com.google.maps.android.awaitStreetViewPanorama instead", + replaceWith = ReplaceWith("awaitStreetViewPanorama()", "com.google.maps.android.awaitStreetViewPanorama"), + level = DeprecationLevel.WARNING +) +public suspend inline fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() + +@Deprecated( + message = "Use com.google.maps.android.cameraChangeEvents instead", + replaceWith = ReplaceWith("cameraChangeEvents()", "com.google.maps.android.cameraChangeEvents"), + level = DeprecationLevel.WARNING +) +public fun StreetViewPanorama.cameraChangeEvents(): Flow = this.canonicalCameraChangeEvents() + +@Deprecated( + message = "Use com.google.maps.android.changeEvents instead", + replaceWith = ReplaceWith("changeEvents()", "com.google.maps.android.changeEvents"), + level = DeprecationLevel.WARNING +) +public fun StreetViewPanorama.changeEvents(): Flow = this.canonicalChangeEvents() + +@Deprecated( + message = "Use com.google.maps.android.clickEvents instead", + replaceWith = ReplaceWith("clickEvents()", "com.google.maps.android.clickEvents"), + level = DeprecationLevel.WARNING +) +public fun StreetViewPanorama.clickEvents(): Flow = this.canonicalClickEvents() + +@Deprecated( + message = "Use com.google.maps.android.longClickEvents instead", + replaceWith = ReplaceWith("longClickEvents()", "com.google.maps.android.longClickEvents"), + level = DeprecationLevel.WARNING +) +public fun StreetViewPanorama.longClickEvents(): Flow = this.canonicalLongClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt b/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt new file mode 100644 index 000000000..4b4313704 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt @@ -0,0 +1,28 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.SupportMapFragment +import com.google.maps.android.awaitMap as canonicalAwaitMap + +@Deprecated( + message = "Use com.google.maps.android.awaitMap instead", + replaceWith = ReplaceWith("awaitMap()", "com.google.maps.android.awaitMap"), + level = DeprecationLevel.WARNING +) +public suspend inline fun SupportMapFragment.awaitMap(): GoogleMap = this.canonicalAwaitMap() diff --git a/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt new file mode 100644 index 000000000..0a4513fbb --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt @@ -0,0 +1,28 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment +import com.google.maps.android.awaitStreetViewPanorama as canonicalAwaitStreetViewPanorama + +@Deprecated( + message = "Use com.google.maps.android.awaitStreetViewPanorama instead", + replaceWith = ReplaceWith("awaitStreetViewPanorama()", "com.google.maps.android.awaitStreetViewPanorama"), + level = DeprecationLevel.WARNING +) +public suspend inline fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() diff --git a/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt b/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt new file mode 100644 index 000000000..b71eb836d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.CameraPosition +import com.google.maps.android.model.cameraPosition as canonical_cameraPosition + +@Deprecated( + message = "Use com.google.maps.android.model.cameraPosition instead", + replaceWith = ReplaceWith("cameraPosition(optionsActions)", "com.google.maps.android.model.cameraPosition"), + level = DeprecationLevel.WARNING +) +public inline fun cameraPosition(optionsActions: CameraPosition.Builder.() -> Unit): CameraPosition = canonical_cameraPosition(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt new file mode 100644 index 000000000..11c06fb46 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.CircleOptions +import com.google.maps.android.model.circleOptions as canonical_circleOptions + +@Deprecated( + message = "Use com.google.maps.android.model.circleOptions instead", + replaceWith = ReplaceWith("circleOptions(optionsActions)", "com.google.maps.android.model.circleOptions"), + level = DeprecationLevel.WARNING +) +public inline fun circleOptions(optionsActions: CircleOptions.() -> Unit): CircleOptions = canonical_circleOptions(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt new file mode 100644 index 000000000..bae64df96 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.maps.android.model.groundOverlayOptions as canonical_groundOverlayOptions + +@Deprecated( + message = "Use com.google.maps.android.model.groundOverlayOptions instead", + replaceWith = ReplaceWith("groundOverlayOptions(optionsActions)", "com.google.maps.android.model.groundOverlayOptions"), + level = DeprecationLevel.WARNING +) +public inline fun groundOverlayOptions(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlayOptions = canonical_groundOverlayOptions(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt new file mode 100644 index 000000000..c1c087362 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.MarkerOptions +import com.google.maps.android.model.markerOptions as canonical_markerOptions + +@Deprecated( + message = "Use com.google.maps.android.model.markerOptions instead", + replaceWith = ReplaceWith("markerOptions(optionsActions)", "com.google.maps.android.model.markerOptions"), + level = DeprecationLevel.WARNING +) +public inline fun markerOptions(optionsActions: MarkerOptions.() -> Unit): MarkerOptions = canonical_markerOptions(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt new file mode 100644 index 000000000..f86f3925e --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.PolygonOptions +import com.google.maps.android.model.polygonOptions as canonical_polygonOptions + +@Deprecated( + message = "Use com.google.maps.android.model.polygonOptions instead", + replaceWith = ReplaceWith("polygonOptions(optionsActions)", "com.google.maps.android.model.polygonOptions"), + level = DeprecationLevel.WARNING +) +public inline fun polygonOptions(optionsActions: PolygonOptions.() -> Unit): PolygonOptions = canonical_polygonOptions(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt new file mode 100644 index 000000000..e3387f4b4 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.PolylineOptions +import com.google.maps.android.model.polylineOptions as canonical_polylineOptions + +@Deprecated( + message = "Use com.google.maps.android.model.polylineOptions instead", + replaceWith = ReplaceWith("polylineOptions(optionsActions)", "com.google.maps.android.model.polylineOptions"), + level = DeprecationLevel.WARNING +) +public inline fun polylineOptions(optionsActions: PolylineOptions.() -> Unit): PolylineOptions = canonical_polylineOptions(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt new file mode 100644 index 000000000..3a32887e3 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.StreetViewPanoramaCamera +import com.google.maps.android.model.streetViewPanoramaCamera as canonical_streetViewPanoramaCamera + +@Deprecated( + message = "Use com.google.maps.android.model.streetViewPanoramaCamera instead", + replaceWith = ReplaceWith("streetViewPanoramaCamera(optionsActions)", "com.google.maps.android.model.streetViewPanoramaCamera"), + level = DeprecationLevel.WARNING +) +public inline fun streetViewPanoramaCamera(optionsActions: StreetViewPanoramaCamera.Builder.() -> Unit): StreetViewPanoramaCamera = canonical_streetViewPanoramaCamera(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt new file mode 100644 index 000000000..9c326acad --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.StreetViewPanoramaOrientation +import com.google.maps.android.model.streetViewPanoramaOrientation as canonical_streetViewPanoramaOrientation + +@Deprecated( + message = "Use com.google.maps.android.model.streetViewPanoramaOrientation instead", + replaceWith = ReplaceWith("streetViewPanoramaOrientation(optionsActions)", "com.google.maps.android.model.streetViewPanoramaOrientation"), + level = DeprecationLevel.WARNING +) +public inline fun streetViewPanoramaOrientation(optionsActions: StreetViewPanoramaOrientation.Builder.() -> Unit): StreetViewPanoramaOrientation = canonical_streetViewPanoramaOrientation(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt new file mode 100644 index 000000000..cc008bb6f --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt @@ -0,0 +1,27 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.TileOverlayOptions +import com.google.maps.android.model.tileOverlayOptions as canonical_tileOverlayOptions + +@Deprecated( + message = "Use com.google.maps.android.model.tileOverlayOptions instead", + replaceWith = ReplaceWith("tileOverlayOptions(optionsActions)", "com.google.maps.android.model.tileOverlayOptions"), + level = DeprecationLevel.WARNING +) +public inline fun tileOverlayOptions(optionsActions: TileOverlayOptions.() -> Unit): TileOverlayOptions = canonical_tileOverlayOptions(optionsActions) diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt b/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt new file mode 100644 index 000000000..a401b441e --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt @@ -0,0 +1,87 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils + +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.component1 as canonicalComponent1 +import com.google.maps.android.component2 as canonicalComponent2 +import com.google.maps.android.isLocationOnPath as canonicalIsLocationOnPath +import com.google.maps.android.isOnEdge as canonicalIsOnEdge +import com.google.maps.android.containsLocation as canonicalContainsLocation +import com.google.maps.android.simplify as canonicalSimplify +import com.google.maps.android.toLatLngList as canonicalToLatLngList +import com.google.maps.android.latLngListEncode as canonicalLatLngListEncode +import com.google.maps.android.isClosedPolygon as canonicalIsClosedPolygon +import com.google.maps.android.sphericalPathLength as canonicalSphericalPathLength +import com.google.maps.android.sphericalPolygonArea as canonicalSphericalPolygonArea +import com.google.maps.android.sphericalPolygonSignedArea as canonicalSphericalPolygonSignedArea +import com.google.maps.android.sphericalHeading as canonicalSphericalHeading +import com.google.maps.android.withSphericalOffset as canonicalWithSphericalOffset +import com.google.maps.android.computeSphericalOffsetOrigin as canonicalComputeSphericalOffsetOrigin +import com.google.maps.android.withSphericalLinearInterpolation as canonicalWithSphericalLinearInterpolation +import com.google.maps.android.sphericalDistance as canonicalSphericalDistance + +@Deprecated("Moved to com.google.maps.android.component1", ReplaceWith("component1()", "com.google.maps.android.component1")) +public inline operator fun LatLng.component1(): Double = this.canonicalComponent1() + +@Deprecated("Moved to com.google.maps.android.component2", ReplaceWith("component2()", "com.google.maps.android.component2")) +public inline operator fun LatLng.component2(): Double = this.canonicalComponent2() + +@Deprecated("Moved to com.google.maps.android.isLocationOnPath", ReplaceWith("isLocationOnPath(latLng, geodesic, tolerance)", "com.google.maps.android.isLocationOnPath")) +public inline fun List.isLocationOnPath(latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1): Boolean = this.canonicalIsLocationOnPath(latLng, geodesic, tolerance) + +@Deprecated("Moved to com.google.maps.android.isOnEdge", ReplaceWith("isOnEdge(latLng, geodesic, tolerance)", "com.google.maps.android.isOnEdge")) +public inline fun List.isOnEdge(latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1): Boolean = this.canonicalIsOnEdge(latLng, geodesic, tolerance) + +@Deprecated("Moved to com.google.maps.android.containsLocation", ReplaceWith("containsLocation(latLng, geodesic)", "com.google.maps.android.containsLocation")) +public inline fun List.containsLocation(latLng: LatLng, geodesic: Boolean): Boolean = this.canonicalContainsLocation(latLng, geodesic) + +@Deprecated("Moved to com.google.maps.android.simplify", ReplaceWith("simplify(tolerance)", "com.google.maps.android.simplify")) +public inline fun List.simplify(tolerance: Double): List = this.canonicalSimplify(tolerance) + +@Deprecated("Moved to com.google.maps.android.toLatLngList", ReplaceWith("toLatLngList()", "com.google.maps.android.toLatLngList")) +public inline fun String.toLatLngList(): List = this.canonicalToLatLngList() + +@Deprecated("Moved to com.google.maps.android.latLngListEncode", ReplaceWith("latLngListEncode()", "com.google.maps.android.latLngListEncode")) +public inline fun List.latLngListEncode(): String = this.canonicalLatLngListEncode() + +@Deprecated("Moved to com.google.maps.android.isClosedPolygon", ReplaceWith("isClosedPolygon()", "com.google.maps.android.isClosedPolygon")) +public inline fun List.isClosedPolygon(): Boolean = this.canonicalIsClosedPolygon() + +@Deprecated("Moved to com.google.maps.android.sphericalPathLength", ReplaceWith("sphericalPathLength()", "com.google.maps.android.sphericalPathLength")) +public inline fun List.sphericalPathLength(): Double = this.canonicalSphericalPathLength() + +@Deprecated("Moved to com.google.maps.android.sphericalPolygonArea", ReplaceWith("sphericalPolygonArea()", "com.google.maps.android.sphericalPolygonArea")) +public inline fun List.sphericalPolygonArea(): Double = this.canonicalSphericalPolygonArea() + +@Deprecated("Moved to com.google.maps.android.sphericalPolygonSignedArea", ReplaceWith("sphericalPolygonSignedArea()", "com.google.maps.android.sphericalPolygonSignedArea")) +public inline fun List.sphericalPolygonSignedArea(): Double = this.canonicalSphericalPolygonSignedArea() + +@Deprecated("Moved to com.google.maps.android.sphericalHeading", ReplaceWith("sphericalHeading(toLatLng)", "com.google.maps.android.sphericalHeading")) +public inline fun LatLng.sphericalHeading(toLatLng: LatLng): Double = this.canonicalSphericalHeading(toLatLng) + +@Deprecated("Moved to com.google.maps.android.withSphericalOffset", ReplaceWith("withSphericalOffset(distance, heading)", "com.google.maps.android.withSphericalOffset")) +public inline fun LatLng.withSphericalOffset(distance: Double, heading: Double): LatLng = this.canonicalWithSphericalOffset(distance, heading) + +@Deprecated("Moved to com.google.maps.android.computeSphericalOffsetOrigin", ReplaceWith("computeSphericalOffsetOrigin(distance, heading)", "com.google.maps.android.computeSphericalOffsetOrigin")) +public inline fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: Double): LatLng? = this.canonicalComputeSphericalOffsetOrigin(distance, heading) + +@Deprecated("Moved to com.google.maps.android.withSphericalLinearInterpolation", ReplaceWith("withSphericalLinearInterpolation(to, fraction)", "com.google.maps.android.withSphericalLinearInterpolation")) +public inline fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: Double): LatLng = this.canonicalWithSphericalLinearInterpolation(to, fraction) + +@Deprecated("Moved to com.google.maps.android.sphericalDistance", ReplaceWith("sphericalDistance(to)", "com.google.maps.android.sphericalDistance")) +public inline fun LatLng.sphericalDistance(to: LatLng): Double = this.canonicalSphericalDistance(to) diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt b/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt new file mode 100644 index 000000000..5d0f2062c --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt @@ -0,0 +1,36 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polygon +import com.google.maps.android.contains as canonicalContains +import com.google.maps.android.isOnEdge as canonicalIsOnEdge +import com.google.maps.android.area as canonicalArea +import com.google.maps.android.signedArea as canonicalSignedArea + +@Deprecated("Moved to com.google.maps.android.contains", ReplaceWith("contains(latLng)", "com.google.maps.android.contains")) +public inline fun Polygon.contains(latLng: LatLng): Boolean = this.canonicalContains(latLng) + +@Deprecated("Moved to com.google.maps.android.isOnEdge", ReplaceWith("isOnEdge(latLng, tolerance)", "com.google.maps.android.isOnEdge")) +public inline fun Polygon.isOnEdge(latLng: LatLng, tolerance: Double = 0.1): Boolean = this.canonicalIsOnEdge(latLng, tolerance) + +@Deprecated("Moved to com.google.maps.android.area", ReplaceWith("area", "com.google.maps.android.area")) +public inline val Polygon.area: Double get() = this.canonicalArea + +@Deprecated("Moved to com.google.maps.android.signedArea", ReplaceWith("signedArea", "com.google.maps.android.signedArea")) +public inline val Polygon.signedArea: Double get() = this.canonicalSignedArea diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt b/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt new file mode 100644 index 000000000..bae32a894 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt @@ -0,0 +1,28 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polyline +import com.google.maps.android.contains as canonicalContains +import com.google.maps.android.sphericalPathLength as canonicalSphericalPathLength + +@Deprecated("Moved to com.google.maps.android.contains", ReplaceWith("contains(latLng, tolerance)", "com.google.maps.android.contains")) +public inline fun Polyline.contains(latLng: LatLng, tolerance: Double = 0.1): Boolean = this.canonicalContains(latLng, tolerance) + +@Deprecated("Moved to com.google.maps.android.sphericalPathLength", ReplaceWith("sphericalPathLength", "com.google.maps.android.sphericalPathLength")) +public inline val Polyline.sphericalPathLength: Double get() = this.canonicalSphericalPathLength diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt b/library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt new file mode 100644 index 000000000..be393f6cd --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.attribution + +import android.content.Context +import androidx.annotation.Keep +import androidx.startup.Initializer +import com.google.android.gms.maps.MapsApiSettings +import com.google.maps.android.utils.meta.AttributionId + +@Keep +@Deprecated( + message = "Use com.google.maps.android.utils.attribution.AttributionIdInitializer instead", + replaceWith = ReplaceWith("AttributionIdInitializer", "com.google.maps.android.utils.attribution.AttributionIdInitializer"), + level = DeprecationLevel.WARNING +) +internal class AttributionIdInitializer : Initializer { + override fun create(context: Context) { + MapsApiSettings.addInternalUsageAttributionId( + context, + AttributionId.VALUE + ) + } + + override fun dependencies(): List>> = emptyList() +} diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt new file mode 100644 index 000000000..de478bdef --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt @@ -0,0 +1,36 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.collection + +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.collections.CircleManager +import com.google.maps.android.collections.addCircle as canonical_addCircle +import com.google.maps.android.collections.clickEvents as canonicalClickEvents +import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents +import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents + +@Deprecated( + message = "Use com.google.maps.android.collections.addCircle instead", + replaceWith = ReplaceWith("addCircle(optionsActions)", "com.google.maps.android.collections.addCircle"), + level = DeprecationLevel.WARNING +) +public inline fun CircleManager.Collection.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = this.canonical_addCircle(optionsActions) + +@Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) +public fun CircleManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt new file mode 100644 index 000000000..ec15411f2 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt @@ -0,0 +1,36 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.collection + +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.GroundOverlayOptions +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.addGroundOverlay as canonical_addGroundOverlay +import com.google.maps.android.collections.clickEvents as canonicalClickEvents +import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents +import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents + +@Deprecated( + message = "Use com.google.maps.android.collections.addGroundOverlay instead", + replaceWith = ReplaceWith("addGroundOverlay(optionsActions)", "com.google.maps.android.collections.addGroundOverlay"), + level = DeprecationLevel.WARNING +) +public inline fun GroundOverlayManager.Collection.addGroundOverlay(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlay = this.canonical_addGroundOverlay(optionsActions) + +@Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) +public fun GroundOverlayManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt new file mode 100644 index 000000000..81283607b --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt @@ -0,0 +1,42 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.collection + +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.addMarker as canonical_addMarker +import com.google.maps.android.collections.clickEvents as canonicalClickEvents +import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents +import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents + +@Deprecated( + message = "Use com.google.maps.android.collections.addMarker instead", + replaceWith = ReplaceWith("addMarker(optionsActions)", "com.google.maps.android.collections.addMarker"), + level = DeprecationLevel.WARNING +) +public inline fun MarkerManager.Collection.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker = this.canonical_addMarker(optionsActions) + +@Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) +public fun MarkerManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() + +@Deprecated("Moved to com.google.maps.android.collections.infoWindowClickEvents", ReplaceWith("infoWindowClickEvents()", "com.google.maps.android.collections.infoWindowClickEvents")) +public fun MarkerManager.Collection.infoWindowClickEvents(): Flow = this.canonicalInfoWindowClickEvents() + +@Deprecated("Moved to com.google.maps.android.collections.infoWindowLongClickEvents", ReplaceWith("infoWindowLongClickEvents()", "com.google.maps.android.collections.infoWindowLongClickEvents")) +public fun MarkerManager.Collection.infoWindowLongClickEvents(): Flow = this.canonicalInfoWindowLongClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt new file mode 100644 index 000000000..00cd36ace --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt @@ -0,0 +1,36 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.collection + +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.addPolygon as canonical_addPolygon +import com.google.maps.android.collections.clickEvents as canonicalClickEvents +import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents +import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents + +@Deprecated( + message = "Use com.google.maps.android.collections.addPolygon instead", + replaceWith = ReplaceWith("addPolygon(optionsActions)", "com.google.maps.android.collections.addPolygon"), + level = DeprecationLevel.WARNING +) +public inline fun PolygonManager.Collection.addPolygon(optionsActions: PolygonOptions.() -> Unit): Polygon = this.canonical_addPolygon(optionsActions) + +@Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) +public fun PolygonManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt new file mode 100644 index 000000000..78011d35a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt @@ -0,0 +1,36 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.collection + +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.collections.PolylineManager +import com.google.maps.android.collections.addPolyline as canonical_addPolyline +import com.google.maps.android.collections.clickEvents as canonicalClickEvents +import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents +import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents + +@Deprecated( + message = "Use com.google.maps.android.collections.addPolyline instead", + replaceWith = ReplaceWith("addPolyline(optionsActions)", "com.google.maps.android.collections.addPolyline"), + level = DeprecationLevel.WARNING +) +public inline fun PolylineManager.Collection.addPolyline(optionsActions: PolylineOptions.() -> Unit): Polyline = this.canonical_addPolyline(optionsActions) + +@Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) +public fun PolylineManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/location/FusedLocationProvider.kt b/library/src/main/java/com/google/maps/android/ktx/utils/location/FusedLocationProvider.kt new file mode 100644 index 000000000..f7bfd9ab7 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/location/FusedLocationProvider.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.location + +import android.Manifest +import android.location.Location +import android.os.Looper +import androidx.annotation.RequiresPermission +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.Priority +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.location.locationEvents as canonicalLocationEvents +import com.google.maps.android.location.fusedLocationEvents as canonicalFusedLocationEvents + +@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) +@Deprecated("Moved to com.google.maps.android.location.locationEvents", ReplaceWith("locationEvents(locationRequest, looper)", "com.google.maps.android.location.locationEvents")) +public fun FusedLocationProviderClient.locationEvents( + locationRequest: LocationRequest, + looper: Looper = Looper.getMainLooper() +): Flow = this.canonicalLocationEvents(locationRequest, looper) + +@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) +@Deprecated("Moved to com.google.maps.android.location.fusedLocationEvents", ReplaceWith("fusedLocationEvents(intervalMs, minUpdateDistanceM, priority, looper)", "com.google.maps.android.location.fusedLocationEvents")) +public fun FusedLocationProviderClient.fusedLocationEvents( + intervalMs: Long = 2000L, + minUpdateDistanceM: Float = 1f, + priority: Int = Priority.PRIORITY_HIGH_ACCURACY, + looper: Looper = Looper.getMainLooper() +): Flow = this.canonicalFusedLocationEvents(intervalMs, minUpdateDistanceM, priority, looper) diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt new file mode 100644 index 000000000..d29f59034 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx.utils.location + +import android.Manifest +import android.location.Location +import android.location.LocationManager +import androidx.annotation.RequiresPermission +import kotlinx.coroutines.flow.Flow +import com.google.maps.android.location.coarseLocationEvents as canonicalCoarseLocationEvents +import com.google.maps.android.location.fineLocationEvents as canonicalFineLocationEvents + +@RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) +@Deprecated("Moved to com.google.maps.android.location.coarseLocationEvents", ReplaceWith("coarseLocationEvents(minTimeMs, minDistanceM)", "com.google.maps.android.location.coarseLocationEvents")) +public fun LocationManager.coarseLocationEvents( + minTimeMs: Long = 1000L, + minDistanceM: Float = 1f +): Flow = this.canonicalCoarseLocationEvents(minTimeMs, minDistanceM) + +@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) +@Deprecated("Moved to com.google.maps.android.location.fineLocationEvents", ReplaceWith("fineLocationEvents(minTimeMs, minDistanceM)", "com.google.maps.android.location.fineLocationEvents")) +public fun LocationManager.fineLocationEvents( + minTimeMs: Long = 1000L, + minDistanceM: Float = 1f +): Flow = this.canonicalFineLocationEvents(minTimeMs, minDistanceM) diff --git a/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt b/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt new file mode 100644 index 000000000..14aa2f98a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.location + +import android.Manifest +import android.location.Location +import android.os.Looper +import androidx.annotation.RequiresPermission +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.Priority +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Returns a cold flow that emits device location updates using [FusedLocationProviderClient.requestLocationUpdates]. + * + * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately + * when the collector cancels or closes the subscription. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active callback completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + * + * @param locationRequest The [LocationRequest] specifying the quality of service (e.g. interval, priority). + * @param looper The [Looper] on which the callback runs. Defaults to [Looper.getMainLooper()]. + */ +@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) +public fun FusedLocationProviderClient.locationEvents( + locationRequest: LocationRequest, + looper: Looper = Looper.getMainLooper() +): Flow = + callbackFlow { + val callback = object : LocationCallback() { + override fun onLocationResult(result: LocationResult) { + for (location in result.locations) { + trySend(location) + } + } + } + + requestLocationUpdates(locationRequest, callback, looper) + + awaitClose { + removeLocationUpdates(callback) + } + } + +/** + * Simplified helper returning a cold flow that emits device location updates from [FusedLocationProviderClient]. + * + * @param intervalMs The desired interval for location updates in milliseconds. Defaults to 2000 ms. + * @param minUpdateDistanceM The minimum distance between location updates in meters. Defaults to 1 meter. + * @param priority The location priority accuracy mode. Defaults to [Priority.PRIORITY_HIGH_ACCURACY]. + * @param looper The [Looper] on which the callback runs. Defaults to [Looper.getMainLooper()]. + */ +@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) +public fun FusedLocationProviderClient.fusedLocationEvents( + intervalMs: Long = 2000L, + minUpdateDistanceM: Float = 1f, + priority: Int = Priority.PRIORITY_HIGH_ACCURACY, + looper: Looper = Looper.getMainLooper() +): Flow { + val request = LocationRequest.Builder(priority, intervalMs) + .setMinUpdateDistanceMeters(minUpdateDistanceM) + .build() + return locationEvents(request, looper) +} diff --git a/library/src/main/java/com/google/maps/android/location/LocationManager.kt b/library/src/main/java/com/google/maps/android/location/LocationManager.kt new file mode 100644 index 000000000..fa2d4919b --- /dev/null +++ b/library/src/main/java/com/google/maps/android/location/LocationManager.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.location + +import android.Manifest +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import androidx.annotation.RequiresPermission +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Returns a cold flow that emits the device's coarse location updates using [LocationManager.NETWORK_PROVIDER] + * (or [LocationManager.PASSIVE_PROVIDER] if network provider is not available). + * + * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately + * when the collector cancels or closes the subscription. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +@RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) +public fun LocationManager.coarseLocationEvents( + minTimeMs: Long = 1000L, + minDistanceM: Float = 1f +): Flow = + callbackFlow { + val listener = object : LocationListener { + override fun onLocationChanged(location: Location) { + trySend(location) + } + + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + // Deprecated in API 29, left empty for backward compatibility with minSdk 23 + } + + override fun onProviderEnabled(provider: String) { + // Left empty for backward compatibility + } + + override fun onProviderDisabled(provider: String) { + close(kotlinx.coroutines.CancellationException("Location provider $provider was disabled")) + } + } + + val provider = if (allProviders.contains(LocationManager.NETWORK_PROVIDER)) { + LocationManager.NETWORK_PROVIDER + } else { + LocationManager.PASSIVE_PROVIDER + } + + requestLocationUpdates(provider, minTimeMs, minDistanceM, listener) + + awaitClose { + removeUpdates(listener) + } + } + +/** + * Returns a cold flow that emits the device's fine location updates using [LocationManager.GPS_PROVIDER]. + * + * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately + * when the collector cancels or closes the subscription. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) +public fun LocationManager.fineLocationEvents( + minTimeMs: Long = 1000L, + minDistanceM: Float = 1f +): Flow = + callbackFlow { + val listener = object : LocationListener { + override fun onLocationChanged(location: Location) { + trySend(location) + } + + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + // Deprecated in API 29, left empty for backward compatibility with minSdk 23 + } + + override fun onProviderEnabled(provider: String) { + // Left empty for backward compatibility + } + + override fun onProviderDisabled(provider: String) { + close(kotlinx.coroutines.CancellationException("Location provider $provider was disabled")) + } + } + + requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, listener) + + awaitClose { + removeUpdates(listener) + } + } diff --git a/library/src/main/java/com/google/maps/android/model/CameraPosition.kt b/library/src/main/java/com/google/maps/android/model/CameraPosition.kt new file mode 100644 index 000000000..4634462d0 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/CameraPosition.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.CameraPosition + +/** + * Builds a new [CameraPosition] using the provided [optionsActions]. Using this removes the need + * to construct a [CameraPosition.Builder] object. + * + * @return the constructed [CameraPosition] + */ +public inline fun cameraPosition(optionsActions: CameraPosition.Builder.() -> Unit): CameraPosition = + CameraPosition.Builder().apply(optionsActions).build() diff --git a/library/src/main/java/com/google/maps/android/model/CircleOptions.kt b/library/src/main/java/com/google/maps/android/model/CircleOptions.kt new file mode 100644 index 000000000..c2622c15a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/CircleOptions.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.CircleOptions + +/** + * Builds a new [CircleOptions] using the provided [optionsActions]. + * + * @return the constructed [CircleOptions] + */ +public inline fun circleOptions(optionsActions: CircleOptions.() -> Unit): CircleOptions = + CircleOptions().apply( + optionsActions + ) diff --git a/library/src/main/java/com/google/maps/android/model/GroundOverlayOptions.kt b/library/src/main/java/com/google/maps/android/model/GroundOverlayOptions.kt new file mode 100644 index 000000000..09bc82699 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/GroundOverlayOptions.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.GroundOverlayOptions + +/** + * Builds a new [GroundOverlayOptions] using the provided [optionsActions]. + * + * @return the constructed [GroundOverlayOptions] + */ +public inline fun groundOverlayOptions(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlayOptions = + GroundOverlayOptions().apply( + optionsActions + ) diff --git a/library/src/main/java/com/google/maps/android/model/MarkerOptions.kt b/library/src/main/java/com/google/maps/android/model/MarkerOptions.kt new file mode 100644 index 000000000..404f9d323 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/MarkerOptions.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.MarkerOptions + +/** + * Builds a new [MarkerOptions] using the provided [optionsActions]. + * + * @return the constructed [MarkerOptions] + */ +public inline fun markerOptions(optionsActions: MarkerOptions.() -> Unit): MarkerOptions = + MarkerOptions().apply( + optionsActions + ) \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/model/PolygonOptions.kt b/library/src/main/java/com/google/maps/android/model/PolygonOptions.kt new file mode 100644 index 000000000..fdb5278e5 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/PolygonOptions.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.PolygonOptions + +/** + * Builds a new [PolygonOptions] using the provided [optionsActions]. + * + * @return the [PolygonOptions] + */ +public inline fun polygonOptions(optionsActions: PolygonOptions.() -> Unit): PolygonOptions = + PolygonOptions().apply( + optionsActions + ) + diff --git a/library/src/main/java/com/google/maps/android/model/PolylineOptions.kt b/library/src/main/java/com/google/maps/android/model/PolylineOptions.kt new file mode 100644 index 000000000..7e8f9678b --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/PolylineOptions.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.PolylineOptions + +/** + * Builds a new [PolylineOptions] using the provided [optionsActions]. + * + * @return the constructed [PolylineOptions] + */ +public inline fun polylineOptions(optionsActions: PolylineOptions.() -> Unit): PolylineOptions = + PolylineOptions().apply( + optionsActions + ) diff --git a/library/src/main/java/com/google/maps/android/model/StreetViewPanoramaCamera.kt b/library/src/main/java/com/google/maps/android/model/StreetViewPanoramaCamera.kt new file mode 100644 index 000000000..b0c1b9280 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/StreetViewPanoramaCamera.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.StreetViewPanoramaCamera + +/** + * Builds a new [StreetViewPanoramaCamera] using the provided [optionsActions]. Using this removes + * the need to construct a [StreetViewPanoramaCamera.Builder] object. + * + * @return the constructed [StreetViewPanoramaCamera] + */ +public inline fun streetViewPanoramaCamera( + optionsActions: StreetViewPanoramaCamera.Builder.() -> Unit +): StreetViewPanoramaCamera = + StreetViewPanoramaCamera.Builder().apply( + optionsActions + ).build() \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/model/StreetViewPanoramaOrientation.kt b/library/src/main/java/com/google/maps/android/model/StreetViewPanoramaOrientation.kt new file mode 100644 index 000000000..20c91f49c --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/StreetViewPanoramaOrientation.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.StreetViewPanoramaOrientation + +/** + * Builds a new [StreetViewPanoramaOrientation] using the provided [optionsActions]. Using this + * removes * the need to construct a [StreetViewPanoramaOrientation.Builder] object. + * + * @return the constructed [StreetViewPanoramaOrientation] + */ +public inline fun streetViewPanoramaOrientation( + optionsActions: StreetViewPanoramaOrientation.Builder.() -> Unit +): StreetViewPanoramaOrientation = + StreetViewPanoramaOrientation.Builder().apply( + optionsActions + ).build() diff --git a/library/src/main/java/com/google/maps/android/model/TileOverlayOptions.kt b/library/src/main/java/com/google/maps/android/model/TileOverlayOptions.kt new file mode 100644 index 000000000..1ddd3afa2 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/model/TileOverlayOptions.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.TileOverlayOptions + +/** + * Builds a new [TileOverlayOptions] using the provided [optionsActions]. + * + * @return the constructed [TileOverlayOptions] + */ +public inline fun tileOverlayOptions(optionsActions: TileOverlayOptions.() -> Unit): TileOverlayOptions = + TileOverlayOptions().apply( + optionsActions + ) diff --git a/library/src/test/java/com/google/maps/android/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt new file mode 100644 index 000000000..1a8409b52 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt @@ -0,0 +1,633 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android + +import android.content.Context +import android.graphics.Bitmap +import android.location.Location +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapsInitializer +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.IndoorBuilding +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.PointOfInterest +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.TileOverlay +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +/** + * Unit test suite for Kotlin extensions on [GoogleMap], covering reactive `Flow` event streams, + * coroutine suspension helpers, and Kotlin DSL option builders. + * + * **Purpose:** + * 1. **Reactive Flows:** Verifies that all 20 Google Map callback listener methods (`setOnMapClickListener`, + * `setOnMarkerClickListener`, `setOnCameraMoveListener`, etc.) are correctly bridged to cold Kotlin `Flow` streams. + * 2. **Coroutine Suspension Helpers:** Verifies that async callback methods (`awaitMapLoad`, + * `awaitAnimateCamera`, `awaitSnapshot`) suspend the coroutine until their callback is completed. + * 3. **DSL Builders:** Verifies that Kotlin inline option builders (`addMarker { ... }`, `addPolyline { ... }`, etc.) + * construct valid option objects and delegate to the underlying [GoogleMap] add-overlay methods. + * + * **How it works:** + * - For **Flow tests**: Uses `runTest` and `launch` to collect the first emitted item (`flow.first()`). + * An [ArgumentCaptor] intercepts the SDK listener registered on [GoogleMap]. Invoking the listener's + * callback method (`onMapClick`, `onMarkerClick`, etc.) emits the test payload into the flow. + * - For **Suspension tests**: Captures the SDK callback (`OnMapLoadedCallback`, `CancelableCallback`, + * or `SnapshotReadyCallback`) and invokes its completion method to resume the suspended coroutine. + * - For **DSL Builders**: Invokes `googleMap.addMarker { ... }` and verifies via Mockito that + * `googleMap.addMarker(any())` was called with the constructed options. + * + * **How we know it is correct:** + * - **Code under test:** Correct if listener callbacks emit the exact mock instance to the Flow/coroutine, + * and if DSL builders delegate to the canonical [GoogleMap] overlay methods without mutation loss. + * - **Test:** Correct because `advanceUntilIdle()` guarantees deterministic coroutine scheduling before + * and after callback invocation, and `assertThat(event).isEqualTo(expected)` confirms exact emission equality. + */ +@ExperimentalCoroutinesApi +@RunWith(MockitoJUnitRunner::class) +public class GoogleMapTest { + + @Mock + private lateinit var googleMap: GoogleMap + + @Mock + private lateinit var marker: Marker + + @Mock + private lateinit var circle: Circle + + @Mock + private lateinit var groundOverlay: GroundOverlay + + @Mock + private lateinit var polygon: Polygon + + @Mock + private lateinit var polyline: Polyline + + @Mock + private lateinit var tileOverlay: TileOverlay + + @Mock + private lateinit var location: Location + + @Mock + private lateinit var bitmap: Bitmap + + @Mock + private lateinit var context: Context + + @Captor + private lateinit var cameraIdleListener: ArgumentCaptor + + @Captor + private lateinit var cameraMoveListener: ArgumentCaptor + + @Captor + private lateinit var cameraMoveStartedListener: ArgumentCaptor + + @Captor + private lateinit var cameraMoveCanceledListener: ArgumentCaptor + + @Captor + private lateinit var mapClickListener: ArgumentCaptor + + @Captor + private lateinit var mapLongClickListener: ArgumentCaptor + + @Captor + private lateinit var markerClickListener: ArgumentCaptor + + @Captor + private lateinit var markerDragListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowClickListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowCloseListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowLongClickListener: ArgumentCaptor + + @Captor + private lateinit var polygonClickListener: ArgumentCaptor + + @Captor + private lateinit var polylineClickListener: ArgumentCaptor + + @Captor + private lateinit var circleClickListener: ArgumentCaptor + + @Captor + private lateinit var groundOverlayClickListener: ArgumentCaptor + + @Captor + private lateinit var poiClickListener: ArgumentCaptor + + @Captor + private lateinit var myLocationClickListener: ArgumentCaptor + + @Captor + private lateinit var myLocationButtonClickListener: ArgumentCaptor + + @Captor + private lateinit var indoorStateChangeListener: ArgumentCaptor + + @Captor + private lateinit var loadedCallback: ArgumentCaptor + + @Captor + private lateinit var cancelableCallback: ArgumentCaptor + + @Mock + private lateinit var cameraUpdate: CameraUpdate + + @Captor + private lateinit var snapshotReadyCallback: ArgumentCaptor + + private lateinit var mapsInitializerMock: MockedStatic + + @Before + public fun setUp() { + mapsInitializerMock = mockStatic(MapsInitializer::class.java) + Mockito.`when`(MapsInitializer.initialize(context)).thenReturn(0) + Mockito.`when`(googleMap.addMarker(any())).thenReturn(marker) + Mockito.`when`(googleMap.addPolyline(any())).thenReturn(polyline) + Mockito.`when`(googleMap.addPolygon(any())).thenReturn(polygon) + Mockito.`when`(googleMap.addCircle(any())).thenReturn(circle) + Mockito.`when`(googleMap.addGroundOverlay(any())).thenReturn(groundOverlay) + Mockito.`when`(googleMap.addTileOverlay(any())).thenReturn(tileOverlay) + } + + @After + public fun tearDown() { + mapsInitializerMock.close() + } + + // --------------------------------------------------------------------------------------------- + // Reactive Coroutine Flow Event Listener Tests + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies [GoogleMap.cameraIdleEvents] converts camera idle callbacks into a Flow. + * **How it works:** Subscribes to `cameraIdleEvents().first()`, captures `OnCameraIdleListener`, and invokes `onCameraIdle()`. + * **How we know it is correct:** Test succeeds if the flow emits when `onCameraIdle()` is called. + */ + @Test + public fun testCameraIdleEvents(): Unit = runTest { + val job = launch { + val event = googleMap.cameraIdleEvents().first() + assertThat(event).isNotNull() + } + advanceUntilIdle() + verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) + cameraIdleListener.value.onCameraIdle() + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.cameraMoveEvents] converts camera move callbacks into a Flow. + * **How it works:** Subscribes to `cameraMoveEvents().first()`, captures `OnCameraMoveListener`, and calls `onCameraMove()`. + * **How we know it is correct:** Test succeeds if the flow emits when `onCameraMove()` is invoked. + */ + @Test + public fun testCameraMoveEvents(): Unit = runTest { + val job = launch { + val event = googleMap.cameraMoveEvents().first() + assertThat(event).isNotNull() + } + advanceUntilIdle() + verify(googleMap).setOnCameraMoveListener(cameraMoveListener.capture()) + cameraMoveListener.value.onCameraMove() + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.cameraMoveStartedEvents] emits camera move start reasons. + * **How it works:** Subscribes to `cameraMoveStartedEvents().first()`, captures listener, and triggers `onCameraMoveStarted(REASON_GESTURE)`. + * **How we know it is correct:** Asserts the emitted integer equals [GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE]. + */ + @Test + public fun testCameraMoveStartedEvents(): Unit = runTest { + val job = launch { + val reason = googleMap.cameraMoveStartedEvents().first() + assertThat(reason).isEqualTo(GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) + } + advanceUntilIdle() + verify(googleMap).setOnCameraMoveStartedListener(cameraMoveStartedListener.capture()) + cameraMoveStartedListener.value.onCameraMoveStarted(GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.cameraMoveCanceledEvents] emits when camera movement is cancelled. + * **How it works:** Subscribes to `cameraMoveCanceledEvents().first()`, captures listener, and calls `onCameraMoveCanceled()`. + * **How we know it is correct:** Asserts the flow emits a non-null Unit event upon cancellation. + */ + @Test + public fun testCameraMoveCanceledEvents(): Unit = runTest { + val job = launch { + val event = googleMap.cameraMoveCanceledEvents().first() + assertThat(event).isNotNull() + } + advanceUntilIdle() + verify(googleMap).setOnCameraMoveCanceledListener(cameraMoveCanceledListener.capture()) + cameraMoveCanceledListener.value.onCameraMoveCanceled() + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.mapClickEvents] emits clicked [LatLng] coordinates. + * **How it works:** Subscribes to `mapClickEvents().first()`, captures `OnMapClickListener`, and calls `onMapClick(target)`. + * **How we know it is correct:** Asserts the emitted coordinates equal the exact `target` LatLng. + */ + @Test + public fun testMapClickEvents(): Unit = runTest { + val target = LatLng(10.0, 20.0) + val job = launch { + val event = googleMap.mapClickEvents().first() + assertThat(event).isEqualTo(target) + } + advanceUntilIdle() + verify(googleMap).setOnMapClickListener(mapClickListener.capture()) + mapClickListener.value.onMapClick(target) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.mapLongClickEvents] emits long-clicked [LatLng] coordinates. + * **How it works:** Subscribes to `mapLongClickEvents().first()`, captures `OnMapLongClickListener`, and calls `onMapLongClick(target)`. + * **How we know it is correct:** Asserts the emitted coordinates equal `target`. + */ + @Test + public fun testMapLongClickEvents(): Unit = runTest { + val target = LatLng(30.0, 40.0) + val job = launch { + val event = googleMap.mapLongClickEvents().first() + assertThat(event).isEqualTo(target) + } + advanceUntilIdle() + verify(googleMap).setOnMapLongClickListener(mapLongClickListener.capture()) + mapLongClickListener.value.onMapLongClick(target) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.markerClickEvents] emits clicked [Marker] instances. + * **How it works:** Subscribes to `markerClickEvents().first()`, captures `OnMarkerClickListener`, and calls `onMarkerClick(marker)`. + * **How we know it is correct:** Asserts the emitted marker equals the mock `marker`. + */ + @Test + public fun testMarkerClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.markerClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(googleMap).setOnMarkerClickListener(markerClickListener.capture()) + markerClickListener.value.onMarkerClick(marker) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.markerDragEvents] emits marker drag events (start, drag, end). + * **How it works:** Subscribes to `markerDragEvents().first()`, captures `OnMarkerDragListener`, and calls `onMarkerDragStart(marker)`. + * **How we know it is correct:** Asserts the emitted drag event contains the mock `marker`. + */ + @Test + public fun testMarkerDragEvents(): Unit = runTest { + val job = launch { + val event = googleMap.markerDragEvents().first() + assertThat(event.marker).isEqualTo(marker) + } + advanceUntilIdle() + verify(googleMap).setOnMarkerDragListener(markerDragListener.capture()) + markerDragListener.value.onMarkerDragStart(marker) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.infoWindowClickEvents] emits markers when their info window is clicked. + * **How it works:** Subscribes to `infoWindowClickEvents().first()`, captures listener, and calls `onInfoWindowClick(marker)`. + * **How we know it is correct:** Asserts the emitted marker equals `marker`. + */ + @Test + public fun testInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.infoWindowClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(googleMap).setOnInfoWindowClickListener(infoWindowClickListener.capture()) + infoWindowClickListener.value.onInfoWindowClick(marker) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.infoWindowCloseEvents] emits markers when their info window closes. + * **How it works:** Subscribes to `infoWindowCloseEvents().first()`, captures listener, and calls `onInfoWindowClose(marker)`. + * **How we know it is correct:** Asserts the emitted marker equals `marker`. + */ + @Test + public fun testInfoWindowCloseEvents(): Unit = runTest { + val job = launch { + val event = googleMap.infoWindowCloseEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(googleMap).setOnInfoWindowCloseListener(infoWindowCloseListener.capture()) + infoWindowCloseListener.value.onInfoWindowClose(marker) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.infoWindowLongClickEvents] emits markers when their info window is long-clicked. + * **How it works:** Subscribes to `infoWindowLongClickEvents().first()`, captures listener, and calls `onInfoWindowLongClick(marker)`. + * **How we know it is correct:** Asserts the emitted marker equals `marker`. + */ + @Test + public fun testInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.infoWindowLongClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(googleMap).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) + infoWindowLongClickListener.value.onInfoWindowLongClick(marker) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.polygonClickEvents] emits clicked [Polygon] instances. + * **How it works:** Subscribes to `polygonClickEvents().first()`, captures listener, and calls `onPolygonClick(polygon)`. + * **How we know it is correct:** Asserts the emitted polygon equals `polygon`. + */ + @Test + public fun testPolygonClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.polygonClickEvents().first() + assertThat(event).isEqualTo(polygon) + } + advanceUntilIdle() + verify(googleMap).setOnPolygonClickListener(polygonClickListener.capture()) + polygonClickListener.value.onPolygonClick(polygon) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.polylineClickEvents] emits clicked [Polyline] instances. + * **How it works:** Subscribes to `polylineClickEvents().first()`, captures listener, and calls `onPolylineClick(polyline)`. + * **How we know it is correct:** Asserts the emitted polyline equals `polyline`. + */ + @Test + public fun testPolylineClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.polylineClickEvents().first() + assertThat(event).isEqualTo(polyline) + } + advanceUntilIdle() + verify(googleMap).setOnPolylineClickListener(polylineClickListener.capture()) + polylineClickListener.value.onPolylineClick(polyline) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.circleClickEvents] emits clicked [Circle] instances. + * **How it works:** Subscribes to `circleClickEvents().first()`, captures listener, and calls `onCircleClick(circle)`. + * **How we know it is correct:** Asserts the emitted circle equals `circle`. + */ + @Test + public fun testCircleClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.circleClickEvents().first() + assertThat(event).isEqualTo(circle) + } + advanceUntilIdle() + verify(googleMap).setOnCircleClickListener(circleClickListener.capture()) + circleClickListener.value.onCircleClick(circle) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.groundOverlayClicks] emits clicked [GroundOverlay] instances. + * **How it works:** Subscribes to `groundOverlayClicks().first()`, captures listener, and calls `onGroundOverlayClick(groundOverlay)`. + * **How we know it is correct:** Asserts the emitted ground overlay equals `groundOverlay`. + */ + @Test + public fun testGroundOverlayClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.groundOverlayClicks().first() + assertThat(event).isEqualTo(groundOverlay) + } + advanceUntilIdle() + verify(googleMap).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) + groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.poiClickEvents] emits clicked [PointOfInterest] instances. + * **How it works:** Subscribes to `poiClickEvents().first()`, captures listener, and calls `onPoiClick(poi)`. + * **How we know it is correct:** Asserts the emitted POI equals the exact `poi` object. + */ + @Test + public fun testPoiClickEvents(): Unit = runTest { + val poi = PointOfInterest(LatLng(1.0, 2.0), "id", "name") + val job = launch { + val event = googleMap.poiClickEvents().first() + assertThat(event).isEqualTo(poi) + } + advanceUntilIdle() + verify(googleMap).setOnPoiClickListener(poiClickListener.capture()) + poiClickListener.value.onPoiClick(poi) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.myLocationClickEvents] emits user [Location] when the location dot is clicked. + * **How it works:** Subscribes to `myLocationClickEvents().first()`, captures listener, and calls `onMyLocationClick(location)`. + * **How we know it is correct:** Asserts the emitted location equals `location`. + */ + @Test + public fun testMyLocationClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.myLocationClickEvents().first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + verify(googleMap).setOnMyLocationClickListener(myLocationClickListener.capture()) + myLocationClickListener.value.onMyLocationClick(location) + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.myLocationButtonClickEvents] emits when the My Location button is clicked. + * **How it works:** Subscribes to `myLocationButtonClickEvents().first()`, captures listener, and calls `onMyLocationButtonClick()`. + * **How we know it is correct:** Asserts the emitted Unit event is non-null. + */ + @Test + public fun testMyLocationButtonClickEvents(): Unit = runTest { + val job = launch { + val event = googleMap.myLocationButtonClickEvents().first() + assertThat(event).isNotNull() + } + advanceUntilIdle() + verify(googleMap).setOnMyLocationButtonClickListener(myLocationButtonClickListener.capture()) + myLocationButtonClickListener.value.onMyLocationButtonClick() + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.indoorStateChangeEvents] emits when an indoor building comes into focus. + * **How it works:** Subscribes to `indoorStateChangeEvents().first()`, captures listener, and calls `onIndoorBuildingFocused()`. + * **How we know it is correct:** Asserts the flow emits a non-null indoor change event. + */ + @Test + public fun testIndoorStateChangeEvents(): Unit = runTest { + val job = launch { + val event = googleMap.indoorStateChangeEvents().first() + assertThat(event).isNotNull() + } + advanceUntilIdle() + verify(googleMap).setOnIndoorStateChangeListener(indoorStateChangeListener.capture()) + indoorStateChangeListener.value.onIndoorBuildingFocused() + job.cancel() + } + + // --------------------------------------------------------------------------------------------- + // Coroutine Suspension Helpers + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies [GoogleMap.awaitMapLoad] suspends until the map finishes rendering. + * **How it works:** Launches `googleMap.awaitMapLoad()`, captures `OnMapLoadedCallback`, and calls `onMapLoaded()`. + * **How we know it is correct:** Verifies `setOnMapLoadedCallback` was registered and coroutine resumes cleanly when invoked. + */ + @Test + public fun testAwaitMapLoad(): Unit = runTest { + val job = launch { + googleMap.awaitMapLoad() + } + advanceUntilIdle() + verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) + loadedCallback.value.onMapLoaded() + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.awaitAnimateCamera] suspends until camera animation finishes. + * **How it works:** Launches `awaitAnimateCamera(cameraUpdate)`, captures `CancelableCallback` from `animateCamera(update, 3000, callback)`, and calls `onFinish()`. + * **How we know it is correct:** Asserts `animateCamera` was called with default 3000ms duration and coroutine resumes upon `onFinish()`. + */ + @Test + public fun testAwaitAnimateCamera(): Unit = runTest { + val job = launch { + googleMap.awaitAnimateCamera(cameraUpdate) + } + advanceUntilIdle() + verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(3000), cancelableCallback.capture()) + cancelableCallback.value.onFinish() + job.cancel() + } + + /** + * **Purpose:** Verifies [GoogleMap.awaitSnapshot] suspends until a bitmap snapshot is ready. + * **How it works:** Stubs `googleMap.snapshot(any(), any())` to immediately invoke `onSnapshotReady(bitmap)`, and calls `awaitSnapshot(bitmap)`. + * **How we know it is correct:** Asserts the returned Bitmap equals the exact `bitmap` mock. + */ + @Test + public fun testAwaitSnapshot(): Unit = runTest { + Mockito.`when`(googleMap.snapshot(any(), any())).thenAnswer { + val cb = it.getArgument(0) + cb.onSnapshotReady(bitmap) + } + var result: Bitmap? = null + val job = launch { + result = googleMap.awaitSnapshot(bitmap) + } + advanceUntilIdle() + assertThat(result).isEqualTo(bitmap) + job.cancel() + } + + // --------------------------------------------------------------------------------------------- + // Kotlin DSL Option Builders + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies all 6 Kotlin DSL option builders (`addMarker`, `addPolyline`, `addPolygon`, `addCircle`, `addGroundOverlay`, `addTileOverlay`) construct valid options and delegate to [GoogleMap]. + * **How it works:** Calls each inline builder block with sample option actions (e.g. `position(LatLng(1, 2))`), and verifies via Mockito that the corresponding `googleMap.add*(any())` method was called. + * **How we know it is correct:** + * - **Code under test:** Correct because the inline builder applies the user DSL lambda to a new options builder and passes it to GoogleMap. + * - **Test:** Fails if any builder fails to delegate to the underlying GoogleMap method or throws an exception. + */ + @Test + public fun testDslBuilders() { + googleMap.addMarker { + position(LatLng(1.0, 2.0)) + } + verify(googleMap).addMarker(any()) + + googleMap.addPolyline { + add(LatLng(1.0, 2.0)) + } + verify(googleMap).addPolyline(any()) + + googleMap.addPolygon { + add(LatLng(1.0, 2.0)) + } + verify(googleMap).addPolygon(any()) + + googleMap.addCircle { + center(LatLng(1.0, 2.0)) + } + verify(googleMap).addCircle(any()) + + googleMap.addGroundOverlay { + zIndex(1f) + clickable(true) + } + verify(googleMap).addGroundOverlay(any()) + + googleMap.addTileOverlay { + fadeIn(true) + } + verify(googleMap).addTileOverlay(any()) + } +} diff --git a/library/src/test/java/com/google/maps/android/LatLngTest.kt b/library/src/test/java/com/google/maps/android/LatLngTest.kt new file mode 100644 index 000000000..2c6238f79 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/LatLngTest.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class LatLngTest { + private val earthRadius = 6371009.0 + + @Test + fun `test that latLng can be destructured`() { + val latLng = LatLng(2.0, 3.0) + val (lat, lng) = latLng + assertThat(lat).isWithin(1e-6).of(2.0) + assertThat(lng).isWithin(1e-6).of(3.0) + } + + @Test + fun `single LatLng encoding`() { + val line = listOf(LatLng(1.0, 2.0)) + assertThat(line.latLngListEncode()).isEqualTo("_ibE_seK") + } + + @Test + fun `single LatLng decoding`() { + val lineEncoded = "_yfyF_ocsF" + val line = lineEncoded.toLatLngList() + assertThat(line.first()).isEqualTo(LatLng(41.0, 40.0)) + } + + @Test + fun `closed polygon true`() { + val latLngList = listOf(LatLng(1.0, 2.0), LatLng(3.0, 4.0), LatLng(1.0, 2.0)) + assertThat(latLngList.isClosedPolygon()).isTrue() + } + + @Test + fun `closed polygon false`() { + val latLngList = listOf(LatLng(1.0, 2.0), LatLng(3.0, 4.0)) + assertThat(latLngList.isClosedPolygon()).isFalse() + } + + @Test + fun `simplify endpoints are still equal`() { + val lineEncoded = "elfjD~a}uNOnFN~Em@fJv@tEMhGDjDe@hG^nF??@lA?n@IvAC`Ay@A{@DwCA{CF_EC{CEi@PBTFDJBJ?V?n@?D@?A@?@?F?F?LAf@?n@@`@@T@~@FpA?fA?p@?r@?vAH`@OR@^ETFJCLD?JA^?J?P?fAC`B@d@?b@A\\@`@Ad@@\\?`@?f@?V?H?DD@DDBBDBD?D?B?B@B@@@B@B@B@D?D?JAF@H@FCLADBDBDCFAN?b@Af@@x@@" + val line = lineEncoded.toLatLngList() + val simplifiedLine = line.simplify(tolerance = 5.0) + assertThat(simplifiedLine).hasSize(20) + assertThat(simplifiedLine.first()).isEqualTo(line.first()) + assertThat(simplifiedLine.last()).isEqualTo(line.last()) + } + + @Test + fun `heading is accurate`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + assertThat(up.sphericalHeading(down)).isWithin(1e-6).of(-180.0) + } + + @Test + fun `withOffset is accurate`() { + val up = LatLng(90.0, 135.0) + val down = up.withSphericalOffset(earthRadius, 180.0) + assertThat(down.latitude).isWithin(1e-6).of(32.704220486917684) + assertThat(down.longitude).isWithin(1e-6).of(-135.0) + } + + @Test + fun `computeOffsetOrigin is accurate`() { + val front = LatLng(0.0, 0.0) + assertThat(front.computeSphericalOffsetOrigin(0.0, 0.0)).isEqualTo(front) + + val result = LatLng(0.0, 45.0).computeSphericalOffsetOrigin( + distance = Math.PI * earthRadius / 4.0, + heading = 90.0 + )!! + assertThat(result.latitude).isWithin(1e-6).of(0.0) + assertThat(result.longitude).isWithin(1e-6).of(0.0) + } + + @Test + fun `compute interpolation`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + + val zeroFraction = up.withSphericalLinearInterpolation(down, 0.0) + assertThat(zeroFraction.latitude).isWithin(1e-6).of(90.0) + assertThat(zeroFraction.longitude).isWithin(1e-6).of(0.0) + + val halfFraction = up.withSphericalLinearInterpolation(down, 0.5) + assertThat(halfFraction.latitude).isWithin(1e-6).of(0.0) + assertThat(halfFraction.longitude).isWithin(1e-6).of(0.0) + + val oneFraction = up.withSphericalLinearInterpolation(down, 1.0) + assertThat(oneFraction.latitude).isWithin(1e-6).of(-90.0) + assertThat(oneFraction.longitude).isWithin(1e-6).of(0.0) + } + + @Test + fun `compute spherical distance`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + assertThat(up.sphericalDistance(down)).isWithin(1e-6).of(Math.PI * earthRadius) + } + + @Test + fun `validate spherical path length`() { + assertThat(emptyList().sphericalPathLength()).isWithin(1e-6).of(0.0) + + val latLngs = listOf(LatLng(0.0, 0.0), LatLng(0.1, 0.1)) + val expectation = earthRadius * Math.sqrt(2.0) * Math.toRadians(0.1) + assertThat(latLngs.sphericalPathLength()).isWithin(1e-1).of(expectation) + } + + @Test + fun `validate spherical polygon area`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + val right = LatLng(0.0, 90.0) + val polygon = listOf(up, down, right, up) + assertThat(polygon.sphericalPolygonArea()).isWithin(1e-6).of(1.2751647824926386E14) + println(polygon.sphericalPolygonSignedArea()) + } + + @Test + fun `validate signed spherical polygon area`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + val right = LatLng(0.0, 90.0) + val polygon = listOf(up, down, right, up) + val reversedPolygon = listOf(up, right, down, up) + assertThat(reversedPolygon.sphericalPolygonSignedArea()) + .isWithin(1e-6) + .of(-polygon.sphericalPolygonSignedArea()) + } + + @Test + fun `contains location evaluates to true`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.containsLocation(LatLng(30.0, 45.0), geodesic = true)).isTrue() + } + + @Test + fun `contains location evaluates to false`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.containsLocation(LatLng(-30.0, 45.0), geodesic = true)).isFalse() + } + + @Test + fun `isOnEdge location evaluates to true`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.isOnEdge(LatLng(0.0, 45.0), geodesic = true)).isTrue() + } + + @Test + fun `isOnEdge location evaluates to false`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.isOnEdge(LatLng(0.0, -45.0), geodesic = true)).isFalse() + } + + @Test + fun `isLocationOnPath location evaluates to true`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(0.0, 180.0)) + assertThat(latLngList.isLocationOnPath(LatLng(0.0, 45.0), geodesic = true)).isTrue() + } + + @Test + fun `isLocationOnPath location evaluates to false`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(0.0, 180.0)) + assertThat(latLngList.isLocationOnPath(LatLng(0.0, -45.0), geodesic = true)).isFalse() + } +} diff --git a/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt b/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt new file mode 100644 index 000000000..e068e3027 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt @@ -0,0 +1,437 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapFragment +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.StreetViewPanoramaFragment +import com.google.android.gms.maps.StreetViewPanoramaView +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment +import com.google.android.gms.maps.model.StreetViewPanoramaCamera +import com.google.android.gms.maps.model.StreetViewPanoramaLocation +import com.google.android.gms.maps.model.StreetViewPanoramaOrientation +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.ktx.awaitMap as ktxAwaitMap +import com.google.maps.android.ktx.awaitStreetViewPanorama as ktxAwaitStreetViewPanorama +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +/** + * Unit test suite for Kotlin coroutine extensions on Map and StreetViewPanorama Fragments/Views. + * + * **Purpose:** + * Tests that asynchronous callback-based Google Maps SDK methods ([SupportMapFragment.getMapAsync], + * [MapView.getMapAsync], [SupportStreetViewPanoramaFragment.getStreetViewPanoramaAsync], etc.) are + * correctly bridged to Kotlin coroutine suspending functions (`awaitMap()`, `awaitStreetViewPanorama()`), + * and that StreetViewPanorama callback listeners are bridged to reactive Kotlin `Flow` streams. + * + * **How it works:** + * 1. For suspending functions (`awaitMap`, `awaitStreetViewPanorama`): Uses `runTest` and `launch` + * to invoke the suspending extension. Intercepts the SDK callback ([OnMapReadyCallback] or + * [OnStreetViewPanoramaReadyCallback]) using a Mockito [ArgumentCaptor]. Invoking `onMapReady(googleMap)` + * resumes the suspended coroutine. + * 2. For Flow extensions (`cameraChangeEvents`, `clickEvents`, etc.): Uses `flow.first()` inside `launch`, + * captures the registered SDK listener via [ArgumentCaptor], and invokes the listener callback to + * emit an item into the flow. + * + * **How we know it is correct:** + * - **Code under test:** Correct if invoking the SDK callback (`onMapReady`, `onStreetViewPanoramaReady`, + * or listener callback) resumes the coroutine or emits to the flow with the exact mock instance. + * - **Test:** Correct because `advanceUntilIdle()` ensures deterministic coroutine scheduling before + * and after callback execution, and `assertThat(result).isEqualTo(expected)` verifies instance equality. + */ +@ExperimentalCoroutinesApi +@RunWith(MockitoJUnitRunner::class) +class MapFragmentExtensionsTest { + + @Mock + private lateinit var googleMap: GoogleMap + + @Mock + private lateinit var streetViewPanorama: StreetViewPanorama + + @Mock + private lateinit var supportMapFragment: SupportMapFragment + + @Mock + private lateinit var mapFragment: MapFragment + + @Mock + private lateinit var mapView: MapView + + @Mock + private lateinit var supportStreetViewPanoramaFragment: SupportStreetViewPanoramaFragment + + @Mock + private lateinit var streetViewPanoramaFragment: StreetViewPanoramaFragment + + @Mock + private lateinit var streetViewPanoramaView: StreetViewPanoramaView + + @Mock + private lateinit var panoramaCamera: StreetViewPanoramaCamera + + @Mock + private lateinit var panoramaLocation: StreetViewPanoramaLocation + + @Mock + private lateinit var panoramaOrientation: StreetViewPanoramaOrientation + + @Captor + private lateinit var onMapReadyCallback: ArgumentCaptor + + @Captor + private lateinit var onStreetViewPanoramaReadyCallback: ArgumentCaptor + + @Captor + private lateinit var cameraChangeListener: ArgumentCaptor + + @Captor + private lateinit var changeListener: ArgumentCaptor + + @Captor + private lateinit var clickListener: ArgumentCaptor + + @Captor + private lateinit var longClickListener: ArgumentCaptor + + // --------------------------------------------------------------------------------------------- + // Canonical awaitMap() tests for SupportMapFragment, MapFragment, and MapView + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies [SupportMapFragment.awaitMap] suspends until [OnMapReadyCallback.onMapReady] is invoked. + * **How it works:** Calls `awaitMap()` in a coroutine, captures `OnMapReadyCallback`, and invokes `onMapReady(googleMap)`. + * **How we know it is correct:** Test asserts `awaitMap()` returns the exact `googleMap` mock passed to `onMapReady`. + */ + @Test + fun testSupportMapFragmentAwaitMap() = runTest { + var map: GoogleMap? = null + val job = launch { + map = supportMapFragment.awaitMap() + } + advanceUntilIdle() + verify(supportMapFragment).getMapAsync(onMapReadyCallback.capture()) + onMapReadyCallback.value.onMapReady(googleMap) + advanceUntilIdle() + assertThat(map).isEqualTo(googleMap) + job.cancel() + } + + /** + * **Purpose:** Verifies [MapFragment.awaitMap] suspends until [OnMapReadyCallback.onMapReady] is invoked. + * **How it works:** Captures `OnMapReadyCallback` on `mapFragment.getMapAsync` and invokes `onMapReady(googleMap)`. + * **How we know it is correct:** Asserts the resumed value equals `googleMap`. + */ + @Test + fun testMapFragmentAwaitMap() = runTest { + var map: GoogleMap? = null + val job = launch { + map = mapFragment.awaitMap() + } + advanceUntilIdle() + verify(mapFragment).getMapAsync(onMapReadyCallback.capture()) + onMapReadyCallback.value.onMapReady(googleMap) + advanceUntilIdle() + assertThat(map).isEqualTo(googleMap) + job.cancel() + } + + /** + * **Purpose:** Verifies [MapView.awaitMap] suspends until [OnMapReadyCallback.onMapReady] is invoked. + * **How it works:** Captures `OnMapReadyCallback` on `mapView.getMapAsync` and invokes `onMapReady(googleMap)`. + * **How we know it is correct:** Asserts the resumed value equals `googleMap`. + */ + @Test + fun testMapViewAwaitMap() = runTest { + var map: GoogleMap? = null + val job = launch { + map = mapView.awaitMap() + } + advanceUntilIdle() + verify(mapView).getMapAsync(onMapReadyCallback.capture()) + onMapReadyCallback.value.onMapReady(googleMap) + advanceUntilIdle() + assertThat(map).isEqualTo(googleMap) + job.cancel() + } + + // --------------------------------------------------------------------------------------------- + // Canonical awaitStreetViewPanorama() tests for SupportStreetViewPanoramaFragment, etc. + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies [SupportStreetViewPanoramaFragment.awaitStreetViewPanorama] suspends until panorama is ready. + * **How it works:** Captures `OnStreetViewPanoramaReadyCallback` on `getStreetViewPanoramaAsync` and calls `onStreetViewPanoramaReady`. + * **How we know it is correct:** Asserts `awaitStreetViewPanorama()` returns the expected `streetViewPanorama` instance. + */ + @Test + fun testSupportStreetViewPanoramaFragmentAwaitPanorama() = runTest { + var panorama: StreetViewPanorama? = null + val job = launch { + panorama = supportStreetViewPanoramaFragment.awaitStreetViewPanorama() + } + advanceUntilIdle() + verify(supportStreetViewPanoramaFragment).getStreetViewPanoramaAsync(onStreetViewPanoramaReadyCallback.capture()) + onStreetViewPanoramaReadyCallback.value.onStreetViewPanoramaReady(streetViewPanorama) + advanceUntilIdle() + assertThat(panorama).isEqualTo(streetViewPanorama) + job.cancel() + } + + /** + * **Purpose:** Verifies [StreetViewPanoramaFragment.awaitStreetViewPanorama] suspends until panorama is ready. + * **How it works:** Captures callback from `streetViewPanoramaFragment.getStreetViewPanoramaAsync` and invokes ready callback. + * **How we know it is correct:** Asserts resumed panorama equals `streetViewPanorama`. + */ + @Test + fun testStreetViewPanoramaFragmentAwaitPanorama() = runTest { + var panorama: StreetViewPanorama? = null + val job = launch { + panorama = streetViewPanoramaFragment.awaitStreetViewPanorama() + } + advanceUntilIdle() + verify(streetViewPanoramaFragment).getStreetViewPanoramaAsync(onStreetViewPanoramaReadyCallback.capture()) + onStreetViewPanoramaReadyCallback.value.onStreetViewPanoramaReady(streetViewPanorama) + advanceUntilIdle() + assertThat(panorama).isEqualTo(streetViewPanorama) + job.cancel() + } + + /** + * **Purpose:** Verifies [StreetViewPanoramaView.awaitStreetViewPanorama] suspends until panorama is ready. + * **How it works:** Captures callback from `streetViewPanoramaView.getStreetViewPanoramaAsync` and invokes ready callback. + * **How we know it is correct:** Asserts resumed panorama equals `streetViewPanorama`. + */ + @Test + fun testStreetViewPanoramaViewAwaitPanorama() = runTest { + var panorama: StreetViewPanorama? = null + val job = launch { + panorama = streetViewPanoramaView.awaitStreetViewPanorama() + } + advanceUntilIdle() + verify(streetViewPanoramaView).getStreetViewPanoramaAsync(onStreetViewPanoramaReadyCallback.capture()) + onStreetViewPanoramaReadyCallback.value.onStreetViewPanoramaReady(streetViewPanorama) + advanceUntilIdle() + assertThat(panorama).isEqualTo(streetViewPanorama) + job.cancel() + } + + // --------------------------------------------------------------------------------------------- + // Backwards-Compatible KTX Shims for awaitMap() and awaitStreetViewPanorama() + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitMap(SupportMapFragment)` forwards to canonical awaitMap(). + * **How it works:** Calls KTX shim in coroutine, invokes captured `OnMapReadyCallback`, and verifies resumption. + * **How we know it is correct:** Asserts KTX shim returns the exact `googleMap` instance without regression. + */ + @Test + fun testKtxSupportMapFragmentAwaitMap() = runTest { + var map: GoogleMap? = null + val job = launch { + map = supportMapFragment.ktxAwaitMap() + } + advanceUntilIdle() + verify(supportMapFragment).getMapAsync(onMapReadyCallback.capture()) + onMapReadyCallback.value.onMapReady(googleMap) + advanceUntilIdle() + assertThat(map).isEqualTo(googleMap) + job.cancel() + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitMap(MapFragment)` forwards to canonical awaitMap(). + * **How it works:** Calls KTX shim in coroutine, invokes captured `OnMapReadyCallback`, and verifies resumption. + * **How we know it is correct:** Asserts KTX shim returns `googleMap`. + */ + @Test + fun testKtxMapFragmentAwaitMap() = runTest { + var map: GoogleMap? = null + val job = launch { + map = mapFragment.ktxAwaitMap() + } + advanceUntilIdle() + verify(mapFragment).getMapAsync(onMapReadyCallback.capture()) + onMapReadyCallback.value.onMapReady(googleMap) + advanceUntilIdle() + assertThat(map).isEqualTo(googleMap) + job.cancel() + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitMap(MapView)` forwards to canonical awaitMap(). + * **How it works:** Calls KTX shim in coroutine, invokes captured `OnMapReadyCallback`, and verifies resumption. + * **How we know it is correct:** Asserts KTX shim returns `googleMap`. + */ + @Test + fun testKtxMapViewAwaitMap() = runTest { + var map: GoogleMap? = null + val job = launch { + map = mapView.ktxAwaitMap() + } + advanceUntilIdle() + verify(mapView).getMapAsync(onMapReadyCallback.capture()) + onMapReadyCallback.value.onMapReady(googleMap) + advanceUntilIdle() + assertThat(map).isEqualTo(googleMap) + job.cancel() + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitStreetViewPanorama(SupportStreetViewPanoramaFragment)` forwards correctly. + * **How it works:** Calls KTX shim in coroutine and triggers captured SDK ready callback. + * **How we know it is correct:** Asserts KTX shim returns `streetViewPanorama`. + */ + @Test + fun testKtxSupportStreetViewPanoramaFragmentAwaitPanorama() = runTest { + var panorama: StreetViewPanorama? = null + val job = launch { + panorama = supportStreetViewPanoramaFragment.ktxAwaitStreetViewPanorama() + } + advanceUntilIdle() + verify(supportStreetViewPanoramaFragment).getStreetViewPanoramaAsync(onStreetViewPanoramaReadyCallback.capture()) + onStreetViewPanoramaReadyCallback.value.onStreetViewPanoramaReady(streetViewPanorama) + advanceUntilIdle() + assertThat(panorama).isEqualTo(streetViewPanorama) + job.cancel() + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitStreetViewPanorama(StreetViewPanoramaFragment)` forwards correctly. + * **How it works:** Calls KTX shim in coroutine and triggers captured SDK ready callback. + * **How we know it is correct:** Asserts KTX shim returns `streetViewPanorama`. + */ + @Test + fun testKtxStreetViewPanoramaFragmentAwaitPanorama() = runTest { + var panorama: StreetViewPanorama? = null + val job = launch { + panorama = streetViewPanoramaFragment.ktxAwaitStreetViewPanorama() + } + advanceUntilIdle() + verify(streetViewPanoramaFragment).getStreetViewPanoramaAsync(onStreetViewPanoramaReadyCallback.capture()) + onStreetViewPanoramaReadyCallback.value.onStreetViewPanoramaReady(streetViewPanorama) + advanceUntilIdle() + assertThat(panorama).isEqualTo(streetViewPanorama) + job.cancel() + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitStreetViewPanorama(StreetViewPanoramaView)` forwards correctly. + * **How it works:** Calls KTX shim in coroutine and triggers captured SDK ready callback. + * **How we know it is correct:** Asserts KTX shim returns `streetViewPanorama`. + */ + @Test + fun testKtxStreetViewPanoramaViewAwaitPanorama() = runTest { + var panorama: StreetViewPanorama? = null + val job = launch { + panorama = streetViewPanoramaView.ktxAwaitStreetViewPanorama() + } + advanceUntilIdle() + verify(streetViewPanoramaView).getStreetViewPanoramaAsync(onStreetViewPanoramaReadyCallback.capture()) + onStreetViewPanoramaReadyCallback.value.onStreetViewPanoramaReady(streetViewPanorama) + advanceUntilIdle() + assertThat(panorama).isEqualTo(streetViewPanorama) + job.cancel() + } + + // --------------------------------------------------------------------------------------------- + // StreetViewPanorama Coroutine Flow Event Listeners + // --------------------------------------------------------------------------------------------- + + /** + * **Purpose:** Verifies [StreetViewPanorama.cameraChangeEvents] converts camera change callbacks to a Kotlin Flow. + * **How it works:** Subscribes to `cameraChangeEvents().first()`, captures SDK listener, and calls `onStreetViewPanoramaCameraChange(panoramaCamera)`. + * **How we know it is correct:** Proves flow emits the exact `panoramaCamera` event passed to the SDK listener. + */ + @Test + fun testStreetViewPanoramaCameraChangeEvents() = runTest { + val job = launch { + val event = streetViewPanorama.cameraChangeEvents().first() + assertThat(event).isEqualTo(panoramaCamera) + } + advanceUntilIdle() + verify(streetViewPanorama).setOnStreetViewPanoramaCameraChangeListener(cameraChangeListener.capture()) + cameraChangeListener.value.onStreetViewPanoramaCameraChange(panoramaCamera) + job.cancel() + } + + /** + * **Purpose:** Verifies [StreetViewPanorama.changeEvents] converts location change callbacks to a Kotlin Flow. + * **How it works:** Subscribes to `changeEvents().first()`, captures SDK listener, and calls `onStreetViewPanoramaChange(panoramaLocation)`. + * **How we know it is correct:** Proves flow emits the exact `panoramaLocation` event. + */ + @Test + fun testStreetViewPanoramaChangeEvents() = runTest { + val job = launch { + val event = streetViewPanorama.changeEvents().first() + assertThat(event).isEqualTo(panoramaLocation) + } + advanceUntilIdle() + verify(streetViewPanorama).setOnStreetViewPanoramaChangeListener(changeListener.capture()) + changeListener.value.onStreetViewPanoramaChange(panoramaLocation) + job.cancel() + } + + /** + * **Purpose:** Verifies [StreetViewPanorama.clickEvents] converts panorama click callbacks to a Kotlin Flow. + * **How it works:** Subscribes to `clickEvents().first()`, captures SDK listener, and calls `onStreetViewPanoramaClick(panoramaOrientation)`. + * **How we know it is correct:** Proves flow emits the clicked `panoramaOrientation`. + */ + @Test + fun testStreetViewPanoramaClickEvents() = runTest { + val job = launch { + val event = streetViewPanorama.clickEvents().first() + assertThat(event).isEqualTo(panoramaOrientation) + } + advanceUntilIdle() + verify(streetViewPanorama).setOnStreetViewPanoramaClickListener(clickListener.capture()) + clickListener.value.onStreetViewPanoramaClick(panoramaOrientation) + job.cancel() + } + + /** + * **Purpose:** Verifies [StreetViewPanorama.longClickEvents] converts panorama long-click callbacks to a Kotlin Flow. + * **How it works:** Subscribes to `longClickEvents().first()`, captures SDK listener, and calls `onStreetViewPanoramaLongClick(panoramaOrientation)`. + * **How we know it is correct:** Proves flow emits the long-clicked `panoramaOrientation`. + */ + @Test + fun testStreetViewPanoramaLongClickEvents() = runTest { + val job = launch { + val event = streetViewPanorama.longClickEvents().first() + assertThat(event).isEqualTo(panoramaOrientation) + } + advanceUntilIdle() + verify(streetViewPanorama).setOnStreetViewPanoramaLongClickListener(longClickListener.capture()) + longClickListener.value.onStreetViewPanoramaLongClick(panoramaOrientation) + job.cancel() + } +} diff --git a/library/src/test/java/com/google/maps/android/PolygonTest.kt b/library/src/test/java/com/google/maps/android/PolygonTest.kt new file mode 100644 index 000000000..74f217af7 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/PolygonTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polygon +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock +import org.junit.Test + +internal class PolygonTest { + @Test + fun testContainsTrue() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.contains(LatLng(1.0, 2.2))).isTrue() + } + + @Test + fun testContainsFalse() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.contains(LatLng(1.01, 2.2))).isFalse() + } + + @Test + fun testIsOnEdgeTrue() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.isOnEdge(LatLng(1.0, 2.2))).isTrue() + + // Tolerance + assertThat(polygon.isOnEdge(LatLng(1.0000005, 2.2))).isTrue() + } + + @Test + fun testIsOnEdgeFalse() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.isOnEdge(LatLng(3.0, 2.2))).isFalse() + } + + private fun mockPolygon(p: List, geodesic: Boolean = true) = mock { + on { points } doReturn p + on { isGeodesic } doReturn geodesic + } +} diff --git a/library/src/test/java/com/google/maps/android/PolylineTest.kt b/library/src/test/java/com/google/maps/android/PolylineTest.kt new file mode 100644 index 000000000..8b2ff32d2 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/PolylineTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polyline +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock +import org.junit.Test + +internal class PolylineTest { + private val earthRadius = 6371009.0 + + @Test + fun `test that contains returns true`() { + val line = mockPolyline(listOf(LatLng(1.0, 0.0), LatLng(3.0, 0.0))) + assertThat(line.contains(LatLng(2.0, 0.0))).isTrue() + } + + @Test + fun `test that contains returns true with tolerance`() { + val line = mockPolyline(listOf(LatLng(1.0, 0.0), LatLng(3.0, 0.0))) + assertThat(line.contains(LatLng(1.0, 0.00000001))).isTrue() + } + + @Test + fun `test that contains returns false`() { + val line = mockPolyline(listOf(LatLng(1.0, 0.0), LatLng(3.0, 0.0))) + assertThat(line.contains(LatLng(4.0, 0.0))).isFalse() + } + + @Test + fun `validate spherical path length`() { + assertThat(mockPolyline(emptyList()).sphericalPathLength).isWithin(1e-6).of(0.0) + val polyline = mockPolyline(listOf(LatLng(0.0, 0.0), LatLng(0.1, 0.1))) + val expectation = earthRadius * Math.sqrt(2.0) * Math.toRadians(0.1) + assertThat(polyline.sphericalPathLength).isWithin(1e-1).of(expectation) + } + + private fun mockPolyline(p: List, geodesic: Boolean = true) = mock { + on { points } doReturn p + on { isGeodesic } doReturn geodesic + } +} diff --git a/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt new file mode 100644 index 000000000..ed7290a25 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt @@ -0,0 +1,250 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.collections +import com.google.maps.android.* + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.Polyline +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.collections.CircleManager +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.Mockito.times +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class CollectionManagersTest { + + @Mock + private lateinit var markerCollection: MarkerManager.Collection + + @Mock + private lateinit var polylineCollection: PolylineManager.Collection + + @Mock + private lateinit var polygonCollection: PolygonManager.Collection + + @Mock + private lateinit var circleCollection: CircleManager.Collection + + @Mock + private lateinit var groundOverlayCollection: GroundOverlayManager.Collection + + @Mock + private lateinit var marker: Marker + + @Mock + private lateinit var polyline: Polyline + + @Mock + private lateinit var polygon: Polygon + + @Mock + private lateinit var circle: Circle + + @Mock + private lateinit var groundOverlay: GroundOverlay + + @Captor + private lateinit var markerClickListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowClickListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowLongClickListener: ArgumentCaptor + + @Captor + private lateinit var polylineClickListener: ArgumentCaptor + + @Captor + private lateinit var polygonClickListener: ArgumentCaptor + + @Captor + private lateinit var circleClickListener: ArgumentCaptor + + @Captor + private lateinit var groundOverlayClickListener: ArgumentCaptor + + private var activeMarkerClickListener: GoogleMap.OnMarkerClickListener? = null + + @Before + public fun setUp() { + activeMarkerClickListener = null + // Stub setOnMarkerClickListener to track the active listener on the collection manager + org.mockito.Mockito.`when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> + activeMarkerClickListener = invocation.arguments[0] as? GoogleMap.OnMarkerClickListener + null + } + } + + @Test + public fun testMarkerCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = markerCollection.clickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + // Trigger the event via our tracked active listener slot! + assertThat(activeMarkerClickListener).isNotNull() + activeMarkerClickListener?.onMarkerClick(marker) + job.cancel() + } + + @Test + public fun testConcurrentCollectionMarkerClick(): Unit = runTest { + val flow = markerCollection.clickEvents() + val collector1Events = mutableListOf() + val collector2Events = mutableListOf() + + // Start Collector 1 + val job1 = launch { + flow.collect { collector1Events.add(it) } + } + advanceUntilIdle() + assertThat(activeMarkerClickListener).isNotNull() + val listener1 = activeMarkerClickListener + + // Start Collector 2 - this will overwrite the listener on the mock + val job2 = launch { + flow.collect { collector2Events.add(it) } + } + advanceUntilIdle() + val listener2 = activeMarkerClickListener + + // Verify they are different listener instances and listener2 hijacked the slot + assertThat(listener1).isNotEqualTo(listener2) + assertThat(activeMarkerClickListener).isEqualTo(listener2) + + // Simulate click via the active listener + activeMarkerClickListener?.onMarkerClick(marker) + advanceUntilIdle() + + // Only collector 2 should receive the event because it hijacked the single-listener slot + assertThat(collector1Events).isEmpty() + assertThat(collector2Events).containsExactly(marker) + + // Cancel collector 1. This triggers awaitClose and clears the listener slot (sets to null) + job1.cancel() + advanceUntilIdle() + + // Assert that the active listener slot is now null! + assertThat(activeMarkerClickListener).isNull() + + // Try to trigger a click again via the active listener (which is now null) + activeMarkerClickListener?.onMarkerClick(marker) + advanceUntilIdle() + + // Collector 2 is now BROKEN and receives no further events because Collector 1's cleanup cleared the shared slot! + assertThat(collector2Events).containsExactly(marker) + + job2.cancel() + } + + @Test + public fun testMarkerCollectionInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = markerCollection.infoWindowClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowClickListener(infoWindowClickListener.capture()) + infoWindowClickListener.value.onInfoWindowClick(marker) + job.cancel() + } + + @Test + public fun testMarkerCollectionInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = markerCollection.infoWindowLongClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) + infoWindowLongClickListener.value.onInfoWindowLongClick(marker) + job.cancel() + } + + @Test + public fun testPolylineCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = polylineCollection.clickEvents().first() + assertThat(event).isEqualTo(polyline) + } + advanceUntilIdle() + verify(polylineCollection).setOnPolylineClickListener(polylineClickListener.capture()) + polylineClickListener.value.onPolylineClick(polyline) + job.cancel() + } + + @Test + public fun testPolygonCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = polygonCollection.clickEvents().first() + assertThat(event).isEqualTo(polygon) + } + advanceUntilIdle() + verify(polygonCollection).setOnPolygonClickListener(polygonClickListener.capture()) + polygonClickListener.value.onPolygonClick(polygon) + job.cancel() + } + + @Test + public fun testCircleCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = circleCollection.clickEvents().first() + assertThat(event).isEqualTo(circle) + } + advanceUntilIdle() + verify(circleCollection).setOnCircleClickListener(circleClickListener.capture()) + circleClickListener.value.onCircleClick(circle) + job.cancel() + } + + @Test + public fun testGroundOverlayCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = groundOverlayCollection.clickEvents().first() + assertThat(event).isEqualTo(groundOverlay) + } + advanceUntilIdle() + verify(groundOverlayCollection).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) + groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) + job.cancel() + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt new file mode 100644 index 000000000..c35b93fad --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt @@ -0,0 +1,114 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import android.content.Context +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapsInitializer +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.Marker +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito +import org.mockito.junit.MockitoJUnitRunner +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.mockito.MockedStatic +import org.mockito.Mockito.mockStatic + +@ExperimentalCoroutinesApi +@RunWith(MockitoJUnitRunner::class) +public class GoogleMapTest { + + @Mock + private lateinit var googleMap: GoogleMap + + @Mock + private lateinit var marker: Marker + + @Mock + private lateinit var context: Context + + @Captor + private lateinit var cameraIdleListener: ArgumentCaptor + + @Captor + private lateinit var loadedCallback: ArgumentCaptor + + @Captor + private lateinit var cancelableCallback: ArgumentCaptor + + private lateinit var mapsInitializerMock: MockedStatic + + @Before + public fun setUp() { + mapsInitializerMock = mockStatic(MapsInitializer::class.java) + Mockito.`when`(MapsInitializer.initialize(context)).thenReturn(0) + } + + @After + public fun tearDown() { + mapsInitializerMock.close() + } + + @Test + public fun testCameraIdleEvents(): Unit = runTest { + val job = launch { + val event = googleMap.cameraIdleEvents().first() + assertThat(event).isNotNull() + } + advanceUntilIdle() + verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) + cameraIdleListener.value.onCameraIdle() + job.cancel() + } + + @Test + public fun testAwaitMapLoad(): Unit = runTest { + val job = launch { + googleMap.awaitMapLoad() + } + advanceUntilIdle() + verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) + loadedCallback.value.onMapLoaded() + job.cancel() + } + + public fun testAwaitAnimateCamera(): Unit = runTest { + val job = launch { + googleMap.awaitAnimateCamera(com.google.android.gms.maps.CameraUpdateFactory.newCameraPosition( + CameraPosition.builder().target(com.google.android.gms.maps.model.LatLng(10.0, 20.0)).zoom(5f).build() + )) + } + advanceUntilIdle() + verify(googleMap).animateCamera(any(CameraUpdate::class.java), cancelableCallback.capture()) + cancelableCallback.value.onFinish() + job.cancel() + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt new file mode 100644 index 000000000..5a5e9463f --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt @@ -0,0 +1,40 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class CameraPositionTest { + + @Test + fun testBuilder() { + val cameraPosition = cameraPosition { + bearing(1f) + target(LatLng(1.0, 2.0)) + tilt(1f) + zoom(12f) + } + assertThat(cameraPosition.bearing).isWithin(1e-6f).of(1f) + assertThat(cameraPosition.target).isEqualTo(LatLng(1.0, 2.0)) + assertThat(cameraPosition.tilt).isWithin(1e-6f).of(1f) + assertThat(cameraPosition.zoom).isWithin(1e-6f).of(12f) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt new file mode 100644 index 000000000..a966b9849 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt @@ -0,0 +1,48 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class CircleOptionsTest { + + @Test + fun testBuilder() { + val circleOptions = circleOptions { + center(LatLng(0.0, 0.0)) + clickable(true) + fillColor(0) + radius(1.23) + strokeColor(1) + strokeWidth(2f) + visible(true) + zIndex(1f) + } + assertThat(circleOptions.center).isEqualTo(LatLng(0.0, 0.0)) + assertThat(circleOptions.isClickable).isTrue() + assertThat(circleOptions.fillColor).isEqualTo(0) + assertThat(circleOptions.radius).isWithin(1e-6).of(1.23) + assertThat(circleOptions.strokeColor).isEqualTo(1) + assertThat(circleOptions.strokeWidth).isEqualTo(2f) + assertThat(circleOptions.isVisible).isTrue() + assertThat(circleOptions.zIndex).isWithin(1e-6f).of(1f) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt new file mode 100644 index 000000000..7be57e3ea --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt @@ -0,0 +1,44 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.mock +import org.junit.Test + +internal class GroundOverlayOptionsTest { + + @Test + fun testBuilder() { + val descriptor: BitmapDescriptor = mock() + val groundOverlayOptions = groundOverlayOptions { + image(descriptor) + bearing(1f) + clickable(true) + transparency(0.5f) + visible(true) + } + assertThat(groundOverlayOptions.image).isEqualTo(descriptor) + assertThat(groundOverlayOptions.bearing).isWithin(1e-6f).of(1f) + assertThat(groundOverlayOptions.isClickable).isTrue() + assertThat(groundOverlayOptions.transparency).isWithin(1e-6f).of(0.5f) + assertThat(groundOverlayOptions.isVisible).isTrue() + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt new file mode 100644 index 000000000..702a1b54d --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt @@ -0,0 +1,46 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class MarkerOptionsTest { + + @Test + fun testBuilder() { + val markerOptions = markerOptions { + position(LatLng(1.0, 2.0)) + alpha(0.5f) + draggable(false) + flat(true) + title("Test") + snippet("Snippet") + visible(true) + } + assertThat(markerOptions.position).isEqualTo(LatLng(1.0, 2.0)) + assertThat(markerOptions.alpha).isWithin(1e-6f).of(0.5f) + assertThat(markerOptions.isDraggable).isFalse() + assertThat(markerOptions.isFlat).isTrue() + assertThat(markerOptions.title).isEqualTo("Test") + assertThat(markerOptions.snippet).isEqualTo("Snippet") + assertThat(markerOptions.isVisible).isTrue() + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt new file mode 100644 index 000000000..c94ced1ac --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt @@ -0,0 +1,39 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import android.graphics.Color +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class PolygonOptionsTest { + @Test + fun testBuilder() { + val polygonOptions = + polygonOptions { + strokeWidth(1.0f) + strokeColor(Color.BLACK) + add(LatLng(1.0, 2.0)) + } + assertThat(polygonOptions.strokeWidth).isWithin(1e-6f).of(1.0f) + assertThat(polygonOptions.strokeColor).isEqualTo(Color.BLACK) + assertThat(polygonOptions.points).containsExactly(LatLng(1.0, 2.0)) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt new file mode 100644 index 000000000..e2ef34057 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt @@ -0,0 +1,42 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class PolylineOptionsTest { + + @Test + fun testBuilder() { + val polylineOptions = polylineOptions { + add(LatLng(1.0, 2.0)) + clickable(true) + color(0) + geodesic(true) + width(1f) + } + assertThat(polylineOptions.points).containsExactly(LatLng(1.0, 2.0)) + assertThat(polylineOptions.isClickable).isTrue() + assertThat(polylineOptions.color).isEqualTo(0) + assertThat(polylineOptions.isGeodesic).isTrue() + assertThat(polylineOptions.width).isWithin(1e-6f).of(1f) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt new file mode 100644 index 000000000..4e77c52f6 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt @@ -0,0 +1,37 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class StreetViewPanoramaCameraTest { + + @Test + fun `test that StreetViewPanoramaCamera is constructed`() { + val camera = streetViewPanoramaCamera { + bearing(1f) + tilt(20f) + zoom(2f) + } + assertThat(camera.bearing).isWithin(1e-6f).of(1f) + assertThat(camera.tilt).isWithin(1e-6f).of(20f) + assertThat(camera.zoom).isWithin(1e-6f).of(2f) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt new file mode 100644 index 000000000..b45af47f9 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt @@ -0,0 +1,35 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class StreetViewPanoramaOrientationTest { + + @Test + fun `test that StreetViewPanoramaOrientation is constructed`() { + val orientation = streetViewPanoramaOrientation { + bearing(1f) + tilt(20f) + } + assertThat(orientation.bearing).isWithin(1e-6f).of(1f) + assertThat(orientation.tilt).isWithin(1e-6f).of(20f) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt new file mode 100644 index 000000000..f438a6a64 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt @@ -0,0 +1,39 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class TileOverlayOptionsTest { + + @Test + fun testBuilder() { + val tileOverlayOptions = tileOverlayOptions { + fadeIn(true) + transparency(0.5f) + visible(false) + zIndex(1f) + } + assertThat(tileOverlayOptions.fadeIn).isTrue() + assertThat(tileOverlayOptions.isVisible).isFalse() + assertThat(tileOverlayOptions.transparency).isWithin(1e-6f).of(0.5f) + assertThat(tileOverlayOptions.zIndex).isWithin(1e-6f).of(1f) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt new file mode 100644 index 000000000..5f37d9269 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt @@ -0,0 +1,190 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class LatLngTest { + private val earthRadius = 6371009.0 + + @Test + fun `test that latLng can be destructured`() { + val latLng = LatLng(2.0, 3.0) + val (lat, lng) = latLng + assertThat(lat).isWithin(1e-6).of(2.0) + assertThat(lng).isWithin(1e-6).of(3.0) + } + + @Test + fun `single LatLng encoding`() { + val line = listOf(LatLng(1.0, 2.0)) + assertThat(line.latLngListEncode()).isEqualTo("_ibE_seK") + } + + @Test + fun `single LatLng decoding`() { + val lineEncoded = "_yfyF_ocsF" + val line = lineEncoded.toLatLngList() + assertThat(line.first()).isEqualTo(LatLng(41.0, 40.0)) + } + + @Test + fun `closed polygon true`() { + val latLngList = listOf(LatLng(1.0, 2.0), LatLng(3.0, 4.0), LatLng(1.0, 2.0)) + assertThat(latLngList.isClosedPolygon()).isTrue() + } + + @Test + fun `closed polygon false`() { + val latLngList = listOf(LatLng(1.0, 2.0), LatLng(3.0, 4.0)) + assertThat(latLngList.isClosedPolygon()).isFalse() + } + + @Test + fun `simplify endpoints are still equal`() { + val lineEncoded = "elfjD~a}uNOnFN~Em@fJv@tEMhGDjDe@hG^nF??@lA?n@IvAC`Ay@A{@DwCA{CF_EC{CEi@PBTFDJBJ?V?n@?D@?A@?@?F?F?LAf@?n@@`@@T@~@FpA?fA?p@?r@?vAH`@OR@^ETFJCLD?JA^?J?P?fAC`B@d@?b@A\\@`@Ad@@\\?`@?f@?V?H?DD@DDBBDBD?D?B?B@B@@@B@B@B@D?D?JAF@H@FCLADBDBDCFAN?b@Af@@x@@" + val line = lineEncoded.toLatLngList() + val simplifiedLine = line.simplify(tolerance = 5.0) + assertThat(simplifiedLine).hasSize(20) + assertThat(simplifiedLine.first()).isEqualTo(line.first()) + assertThat(simplifiedLine.last()).isEqualTo(line.last()) + } + + @Test + fun `heading is accurate`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + assertThat(up.sphericalHeading(down)).isWithin(1e-6).of(-180.0) + } + + @Test + fun `withOffset is accurate`() { + val up = LatLng(90.0, 135.0) + val down = up.withSphericalOffset(earthRadius, 180.0) + assertThat(down.latitude).isWithin(1e-6).of(32.704220486917684) + assertThat(down.longitude).isWithin(1e-6).of(-135.0) + } + + @Test + fun `computeOffsetOrigin is accurate`() { + val front = LatLng(0.0, 0.0) + assertThat(front.computeSphericalOffsetOrigin(0.0, 0.0)).isEqualTo(front) + + val result = LatLng(0.0, 45.0).computeSphericalOffsetOrigin( + distance = Math.PI * earthRadius / 4.0, + heading = 90.0 + )!! + assertThat(result.latitude).isWithin(1e-6).of(0.0) + assertThat(result.longitude).isWithin(1e-6).of(0.0) + } + + @Test + fun `compute interpolation`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + + val zeroFraction = up.withSphericalLinearInterpolation(down, 0.0) + assertThat(zeroFraction.latitude).isWithin(1e-6).of(90.0) + assertThat(zeroFraction.longitude).isWithin(1e-6).of(0.0) + + val halfFraction = up.withSphericalLinearInterpolation(down, 0.5) + assertThat(halfFraction.latitude).isWithin(1e-6).of(0.0) + assertThat(halfFraction.longitude).isWithin(1e-6).of(0.0) + + val oneFraction = up.withSphericalLinearInterpolation(down, 1.0) + assertThat(oneFraction.latitude).isWithin(1e-6).of(-90.0) + assertThat(oneFraction.longitude).isWithin(1e-6).of(0.0) + } + + @Test + fun `compute spherical distance`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + assertThat(up.sphericalDistance(down)).isWithin(1e-6).of(Math.PI * earthRadius) + } + + @Test + fun `validate spherical path length`() { + assertThat(emptyList().sphericalPathLength()).isWithin(1e-6).of(0.0) + + val latLngs = listOf(LatLng(0.0, 0.0), LatLng(0.1, 0.1)) + val expectation = earthRadius * Math.sqrt(2.0) * Math.toRadians(0.1) + assertThat(latLngs.sphericalPathLength()).isWithin(1e-1).of(expectation) + } + + @Test + fun `validate spherical polygon area`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + val right = LatLng(0.0, 90.0) + val polygon = listOf(up, down, right, up) + assertThat(polygon.sphericalPolygonArea()).isWithin(1e-6).of(1.2751647824926386E14) + println(polygon.sphericalPolygonSignedArea()) + } + + @Test + fun `validate signed spherical polygon area`() { + val up = LatLng(90.0, 0.0) + val down = LatLng(-90.0, 0.0) + val right = LatLng(0.0, 90.0) + val polygon = listOf(up, down, right, up) + val reversedPolygon = listOf(up, right, down, up) + assertThat(reversedPolygon.sphericalPolygonSignedArea()) + .isWithin(1e-6) + .of(-polygon.sphericalPolygonSignedArea()) + } + + @Test + fun `contains location evaluates to true`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.containsLocation(LatLng(30.0, 45.0), geodesic = true)).isTrue() + } + + @Test + fun `contains location evaluates to false`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.containsLocation(LatLng(-30.0, 45.0), geodesic = true)).isFalse() + } + + @Test + fun `isOnEdge location evaluates to true`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.isOnEdge(LatLng(0.0, 45.0), geodesic = true)).isTrue() + } + + @Test + fun `isOnEdge location evaluates to false`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(-90.0, 45.0)) + assertThat(latLngList.isOnEdge(LatLng(0.0, -45.0), geodesic = true)).isFalse() + } + + @Test + fun `isLocationOnPath location evaluates to true`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(0.0, 180.0)) + assertThat(latLngList.isLocationOnPath(LatLng(0.0, 45.0), geodesic = true)).isTrue() + } + + @Test + fun `isLocationOnPath location evaluates to false`() { + val latLngList = listOf(LatLng(0.0, 0.0), LatLng(0.0, 90.0), LatLng(0.0, 180.0)) + assertThat(latLngList.isLocationOnPath(LatLng(0.0, -45.0), geodesic = true)).isFalse() + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt new file mode 100644 index 000000000..7c41b07c6 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt @@ -0,0 +1,60 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polygon +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock +import org.junit.Test + +internal class PolygonTest { + @Test + fun testContainsTrue() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.contains(LatLng(1.0, 2.2))).isTrue() + } + + @Test + fun testContainsFalse() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.contains(LatLng(1.01, 2.2))).isFalse() + } + + @Test + fun testIsOnEdgeTrue() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.isOnEdge(LatLng(1.0, 2.2))).isTrue() + + // Tolerance + assertThat(polygon.isOnEdge(LatLng(1.0000005, 2.2))).isTrue() + } + + @Test + fun testIsOnEdgeFalse() { + val polygon = mockPolygon(listOf(LatLng(1.0, 2.2), LatLng(0.0, 1.0))) + assertThat(polygon.isOnEdge(LatLng(3.0, 2.2))).isFalse() + } + + private fun mockPolygon(p: List, geodesic: Boolean = true) = mock { + on { points } doReturn p + on { isGeodesic } doReturn geodesic + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt new file mode 100644 index 000000000..89cc27819 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt @@ -0,0 +1,61 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polyline +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock +import org.junit.Test + +internal class PolylineTest { + private val earthRadius = 6371009.0 + + @Test + fun `test that contains returns true`() { + val line = mockPolyline(listOf(LatLng(1.0, 0.0), LatLng(3.0, 0.0))) + assertThat(line.contains(LatLng(2.0, 0.0))).isTrue() + } + + @Test + fun `test that contains returns true with tolerance`() { + val line = mockPolyline(listOf(LatLng(1.0, 0.0), LatLng(3.0, 0.0))) + assertThat(line.contains(LatLng(1.0, 0.00000001))).isTrue() + } + + @Test + fun `test that contains returns false`() { + val line = mockPolyline(listOf(LatLng(1.0, 0.0), LatLng(3.0, 0.0))) + assertThat(line.contains(LatLng(4.0, 0.0))).isFalse() + } + + @Test + fun `validate spherical path length`() { + assertThat(mockPolyline(emptyList()).sphericalPathLength).isWithin(1e-6).of(0.0) + val polyline = mockPolyline(listOf(LatLng(0.0, 0.0), LatLng(0.1, 0.1))) + val expectation = earthRadius * Math.sqrt(2.0) * Math.toRadians(0.1) + assertThat(polyline.sphericalPathLength).isWithin(1e-1).of(expectation) + } + + private fun mockPolyline(p: List, geodesic: Boolean = true) = mock { + on { points } doReturn p + on { isGeodesic } doReturn geodesic + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt new file mode 100644 index 000000000..690ac0734 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt @@ -0,0 +1,251 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils.collection +import com.google.maps.android.ktx.* + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.Polyline +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.collections.CircleManager +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.Mockito.times +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class CollectionManagersTest { + + @Mock + private lateinit var markerCollection: MarkerManager.Collection + + @Mock + private lateinit var polylineCollection: PolylineManager.Collection + + @Mock + private lateinit var polygonCollection: PolygonManager.Collection + + @Mock + private lateinit var circleCollection: CircleManager.Collection + + @Mock + private lateinit var groundOverlayCollection: GroundOverlayManager.Collection + + @Mock + private lateinit var marker: Marker + + @Mock + private lateinit var polyline: Polyline + + @Mock + private lateinit var polygon: Polygon + + @Mock + private lateinit var circle: Circle + + @Mock + private lateinit var groundOverlay: GroundOverlay + + @Captor + private lateinit var markerClickListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowClickListener: ArgumentCaptor + + @Captor + private lateinit var infoWindowLongClickListener: ArgumentCaptor + + @Captor + private lateinit var polylineClickListener: ArgumentCaptor + + @Captor + private lateinit var polygonClickListener: ArgumentCaptor + + @Captor + private lateinit var circleClickListener: ArgumentCaptor + + @Captor + private lateinit var groundOverlayClickListener: ArgumentCaptor + + private var activeMarkerClickListener: GoogleMap.OnMarkerClickListener? = null + + @Before + public fun setUp() { + activeMarkerClickListener = null + // Stub setOnMarkerClickListener to track the active listener on the collection manager + org.mockito.Mockito.`when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> + activeMarkerClickListener = invocation.arguments[0] as? GoogleMap.OnMarkerClickListener + null + } + } + + @Test + public fun testMarkerCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = markerCollection.clickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + // Trigger the event via our tracked active listener slot! + assertThat(activeMarkerClickListener).isNotNull() + activeMarkerClickListener?.onMarkerClick(marker) + job.cancel() + } + + @Test + public fun testConcurrentCollectionMarkerClick(): Unit = runTest { + val flow = markerCollection.clickEvents() + val collector1Events = mutableListOf() + val collector2Events = mutableListOf() + + // Start Collector 1 + val job1 = launch { + flow.collect { collector1Events.add(it) } + } + advanceUntilIdle() + assertThat(activeMarkerClickListener).isNotNull() + val listener1 = activeMarkerClickListener + + // Start Collector 2 - this will overwrite the listener on the mock + val job2 = launch { + flow.collect { collector2Events.add(it) } + } + advanceUntilIdle() + val listener2 = activeMarkerClickListener + + // Verify they are different listener instances and listener2 hijacked the slot + assertThat(listener1).isNotEqualTo(listener2) + assertThat(activeMarkerClickListener).isEqualTo(listener2) + + // Simulate click via the active listener + activeMarkerClickListener?.onMarkerClick(marker) + advanceUntilIdle() + + // Only collector 2 should receive the event because it hijacked the single-listener slot + assertThat(collector1Events).isEmpty() + assertThat(collector2Events).containsExactly(marker) + + // Cancel collector 1. This triggers awaitClose and clears the listener slot (sets to null) + job1.cancel() + advanceUntilIdle() + + // Assert that the active listener slot is now null! + assertThat(activeMarkerClickListener).isNull() + + // Try to trigger a click again via the active listener (which is now null) + activeMarkerClickListener?.onMarkerClick(marker) + advanceUntilIdle() + + // Collector 2 is now BROKEN and receives no further events because Collector 1's cleanup cleared the shared slot! + assertThat(collector2Events).containsExactly(marker) + + job2.cancel() + } + + @Test + public fun testMarkerCollectionInfoWindowClickEvents(): Unit = runTest { + val job = launch { + val event = markerCollection.infoWindowClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowClickListener(infoWindowClickListener.capture()) + infoWindowClickListener.value.onInfoWindowClick(marker) + job.cancel() + } + + @Test + public fun testMarkerCollectionInfoWindowLongClickEvents(): Unit = runTest { + val job = launch { + val event = markerCollection.infoWindowLongClickEvents().first() + assertThat(event).isEqualTo(marker) + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) + infoWindowLongClickListener.value.onInfoWindowLongClick(marker) + job.cancel() + } + + @Test + public fun testPolylineCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = polylineCollection.clickEvents().first() + assertThat(event).isEqualTo(polyline) + } + advanceUntilIdle() + verify(polylineCollection).setOnPolylineClickListener(polylineClickListener.capture()) + polylineClickListener.value.onPolylineClick(polyline) + job.cancel() + } + + @Test + public fun testPolygonCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = polygonCollection.clickEvents().first() + assertThat(event).isEqualTo(polygon) + } + advanceUntilIdle() + verify(polygonCollection).setOnPolygonClickListener(polygonClickListener.capture()) + polygonClickListener.value.onPolygonClick(polygon) + job.cancel() + } + + @Test + public fun testCircleCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = circleCollection.clickEvents().first() + assertThat(event).isEqualTo(circle) + } + advanceUntilIdle() + verify(circleCollection).setOnCircleClickListener(circleClickListener.capture()) + circleClickListener.value.onCircleClick(circle) + job.cancel() + } + + @Test + public fun testGroundOverlayCollectionClickEvents(): Unit = runTest { + val job = launch { + val event = groundOverlayCollection.clickEvents().first() + assertThat(event).isEqualTo(groundOverlay) + } + advanceUntilIdle() + verify(groundOverlayCollection).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) + groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) + job.cancel() + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt new file mode 100644 index 000000000..7ad579baf --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt @@ -0,0 +1,117 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils.location + +import android.annotation.SuppressLint +import android.location.Location +import android.os.Looper +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.Priority +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class FusedLocationProviderTest { + + @Mock + private lateinit var fusedLocationClient: FusedLocationProviderClient + + @Mock + private lateinit var location: Location + + @Mock + private lateinit var looper: Looper + + @Captor + private lateinit var locationCallbackCaptor: ArgumentCaptor + + @SuppressLint("MissingPermission") + @Test + public fun testLocationEvents(): Unit = runTest { + val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L).build() + + val job = launch { + val event = fusedLocationClient.locationEvents(request, looper).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + verify(fusedLocationClient).requestLocationUpdates( + eq(request), + locationCallbackCaptor.capture(), + eq(looper) + ) + + val result = LocationResult.create(listOf(location)) + locationCallbackCaptor.value.onLocationResult(result) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + verify(fusedLocationClient).removeLocationUpdates(eq(locationCallbackCaptor.value)) + } + + @SuppressLint("MissingPermission") + @Test + public fun testFusedLocationEvents(): Unit = runTest { + val job = launch { + val event = fusedLocationClient.fusedLocationEvents( + intervalMs = 2000L, + minUpdateDistanceM = 5f, + priority = Priority.PRIORITY_BALANCED_POWER_ACCURACY, + looper = looper + ).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + verify(fusedLocationClient).requestLocationUpdates( + any(LocationRequest::class.java), + locationCallbackCaptor.capture(), + eq(looper) + ) + + val result = LocationResult.create(listOf(location)) + locationCallbackCaptor.value.onLocationResult(result) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + verify(fusedLocationClient).removeLocationUpdates(eq(locationCallbackCaptor.value)) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt new file mode 100644 index 000000000..c535d55f7 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt @@ -0,0 +1,150 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.ktx.utils.location + +import android.annotation.SuppressLint +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class LocationManagerTest { + + @Mock + private lateinit var locationManager: LocationManager + + @Mock + private lateinit var location: Location + + @Captor + private lateinit var locationListenerCaptor: ArgumentCaptor + + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationEvents(): Unit = runTest { + // Setup providers mock + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val job = launch { + val event = locationManager.coarseLocationEvents(1000L, 1f).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + // Verify registered with correct provider and capture listener + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + eq(1000L), + eq(1f), + locationListenerCaptor.capture() + ) + + // Trigger event + locationListenerCaptor.value.onLocationChanged(location) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + // Verify cleanup + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + } + + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationProviderDisabled(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val exceptions = mutableListOf() + val job = launch { + try { + locationManager.coarseLocationEvents(1000L, 1f).collect {} + } catch (e: Throwable) { + exceptions.add(e) + } + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + anyLong(), + anyFloat(), + locationListenerCaptor.capture() + ) + + // Simulate provider disablement! + locationListenerCaptor.value.onProviderDisabled(LocationManager.NETWORK_PROVIDER) + advanceUntilIdle() + + // Verify that the flow threw a CancellationException and terminated cleanly + assertThat(exceptions).hasSize(1) + assertThat(exceptions.first()).isInstanceOf(kotlinx.coroutines.CancellationException::class.java) + assertThat(exceptions.first().message).contains("Location provider ${LocationManager.NETWORK_PROVIDER} was disabled") + + // Verify cleanup runs automatically on closure! + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + job.cancel() + } + + @SuppressLint("MissingPermission") + @Test + public fun testFineLocationEvents(): Unit = runTest { + val job = launch { + val event = locationManager.fineLocationEvents(2000L, 2f).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + // Verify registered with GPS provider + verify(locationManager).requestLocationUpdates( + eq(LocationManager.GPS_PROVIDER), + eq(2000L), + eq(2f), + locationListenerCaptor.capture() + ) + + // Trigger event + locationListenerCaptor.value.onLocationChanged(location) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + // Verify cleanup + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + } +} diff --git a/library/src/test/java/com/google/maps/android/location/FusedLocationProviderTest.kt b/library/src/test/java/com/google/maps/android/location/FusedLocationProviderTest.kt new file mode 100644 index 000000000..a9ac1a50c --- /dev/null +++ b/library/src/test/java/com/google/maps/android/location/FusedLocationProviderTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.location + +import android.annotation.SuppressLint +import android.location.Location +import android.os.Looper +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.Priority +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class FusedLocationProviderTest { + + @Mock + private lateinit var fusedLocationClient: FusedLocationProviderClient + + @Mock + private lateinit var location: Location + + @Mock + private lateinit var looper: Looper + + @Captor + private lateinit var locationCallbackCaptor: ArgumentCaptor + + @SuppressLint("MissingPermission") + @Test + public fun testLocationEvents(): Unit = runTest { + val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L).build() + + val job = launch { + val event = fusedLocationClient.locationEvents(request, looper).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + verify(fusedLocationClient).requestLocationUpdates( + eq(request), + locationCallbackCaptor.capture(), + eq(looper) + ) + + val result = LocationResult.create(listOf(location)) + locationCallbackCaptor.value.onLocationResult(result) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + verify(fusedLocationClient).removeLocationUpdates(eq(locationCallbackCaptor.value)) + } + + @SuppressLint("MissingPermission") + @Test + public fun testFusedLocationEvents(): Unit = runTest { + val job = launch { + val event = fusedLocationClient.fusedLocationEvents( + intervalMs = 2000L, + minUpdateDistanceM = 5f, + priority = Priority.PRIORITY_BALANCED_POWER_ACCURACY, + looper = looper + ).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + verify(fusedLocationClient).requestLocationUpdates( + any(LocationRequest::class.java), + locationCallbackCaptor.capture(), + eq(looper) + ) + + val result = LocationResult.create(listOf(location)) + locationCallbackCaptor.value.onLocationResult(result) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + verify(fusedLocationClient).removeLocationUpdates(eq(locationCallbackCaptor.value)) + } +} diff --git a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt new file mode 100644 index 000000000..c03c6d368 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.location + +import android.annotation.SuppressLint +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class LocationManagerTest { + + @Mock + private lateinit var locationManager: LocationManager + + @Mock + private lateinit var location: Location + + @Captor + private lateinit var locationListenerCaptor: ArgumentCaptor + + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationEvents(): Unit = runTest { + // Setup providers mock + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val job = launch { + val event = locationManager.coarseLocationEvents(1000L, 1f).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + // Verify registered with correct provider and capture listener + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + eq(1000L), + eq(1f), + locationListenerCaptor.capture() + ) + + // Trigger event + locationListenerCaptor.value.onLocationChanged(location) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + // Verify cleanup + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + } + + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationProviderDisabled(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val exceptions = mutableListOf() + val job = launch { + try { + locationManager.coarseLocationEvents(1000L, 1f).collect {} + } catch (e: Throwable) { + exceptions.add(e) + } + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + anyLong(), + anyFloat(), + locationListenerCaptor.capture() + ) + + // Simulate provider disablement! + locationListenerCaptor.value.onProviderDisabled(LocationManager.NETWORK_PROVIDER) + advanceUntilIdle() + + // Verify that the flow threw a CancellationException and terminated cleanly + assertThat(exceptions).hasSize(1) + assertThat(exceptions.first()).isInstanceOf(kotlinx.coroutines.CancellationException::class.java) + assertThat(exceptions.first().message).contains("Location provider ${LocationManager.NETWORK_PROVIDER} was disabled") + + // Verify cleanup runs automatically on closure! + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + job.cancel() + } + + @SuppressLint("MissingPermission") + @Test + public fun testFineLocationEvents(): Unit = runTest { + val job = launch { + val event = locationManager.fineLocationEvents(2000L, 2f).first() + assertThat(event).isEqualTo(location) + } + advanceUntilIdle() + + // Verify registered with GPS provider + verify(locationManager).requestLocationUpdates( + eq(LocationManager.GPS_PROVIDER), + eq(2000L), + eq(2f), + locationListenerCaptor.capture() + ) + + // Trigger event + locationListenerCaptor.value.onLocationChanged(location) + advanceUntilIdle() + + job.cancel() + advanceUntilIdle() + + // Verify cleanup + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/CameraPositionTest.kt b/library/src/test/java/com/google/maps/android/model/CameraPositionTest.kt new file mode 100644 index 000000000..b00873a2a --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/CameraPositionTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class CameraPositionTest { + + @Test + fun testBuilder() { + val cameraPosition = cameraPosition { + bearing(1f) + target(LatLng(1.0, 2.0)) + tilt(1f) + zoom(12f) + } + assertThat(cameraPosition.bearing).isWithin(1e-6f).of(1f) + assertThat(cameraPosition.target).isEqualTo(LatLng(1.0, 2.0)) + assertThat(cameraPosition.tilt).isWithin(1e-6f).of(1f) + assertThat(cameraPosition.zoom).isWithin(1e-6f).of(12f) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/CircleOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/CircleOptionsTest.kt new file mode 100644 index 000000000..faa2dbabb --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/CircleOptionsTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class CircleOptionsTest { + + @Test + fun testBuilder() { + val circleOptions = circleOptions { + center(LatLng(0.0, 0.0)) + clickable(true) + fillColor(0) + radius(1.23) + strokeColor(1) + strokeWidth(2f) + visible(true) + zIndex(1f) + } + assertThat(circleOptions.center).isEqualTo(LatLng(0.0, 0.0)) + assertThat(circleOptions.isClickable).isTrue() + assertThat(circleOptions.fillColor).isEqualTo(0) + assertThat(circleOptions.radius).isWithin(1e-6).of(1.23) + assertThat(circleOptions.strokeColor).isEqualTo(1) + assertThat(circleOptions.strokeWidth).isEqualTo(2f) + assertThat(circleOptions.isVisible).isTrue() + assertThat(circleOptions.zIndex).isWithin(1e-6f).of(1f) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt new file mode 100644 index 000000000..1344212e3 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.mock +import org.junit.Test + +internal class GroundOverlayOptionsTest { + + @Test + fun testBuilder() { + val descriptor: BitmapDescriptor = mock() + val groundOverlayOptions = groundOverlayOptions { + image(descriptor) + bearing(1f) + clickable(true) + transparency(0.5f) + visible(true) + } + assertThat(groundOverlayOptions.image).isEqualTo(descriptor) + assertThat(groundOverlayOptions.bearing).isWithin(1e-6f).of(1f) + assertThat(groundOverlayOptions.isClickable).isTrue() + assertThat(groundOverlayOptions.transparency).isWithin(1e-6f).of(0.5f) + assertThat(groundOverlayOptions.isVisible).isTrue() + } +} diff --git a/library/src/test/java/com/google/maps/android/model/MarkerOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/MarkerOptionsTest.kt new file mode 100644 index 000000000..57652a6f0 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/MarkerOptionsTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class MarkerOptionsTest { + + @Test + fun testBuilder() { + val markerOptions = markerOptions { + position(LatLng(1.0, 2.0)) + alpha(0.5f) + draggable(false) + flat(true) + title("Test") + snippet("Snippet") + visible(true) + } + assertThat(markerOptions.position).isEqualTo(LatLng(1.0, 2.0)) + assertThat(markerOptions.alpha).isWithin(1e-6f).of(0.5f) + assertThat(markerOptions.isDraggable).isFalse() + assertThat(markerOptions.isFlat).isTrue() + assertThat(markerOptions.title).isEqualTo("Test") + assertThat(markerOptions.snippet).isEqualTo("Snippet") + assertThat(markerOptions.isVisible).isTrue() + } +} diff --git a/library/src/test/java/com/google/maps/android/model/PolygonOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/PolygonOptionsTest.kt new file mode 100644 index 000000000..d817def05 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/PolygonOptionsTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import android.graphics.Color +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class PolygonOptionsTest { + @Test + fun testBuilder() { + val polygonOptions = + polygonOptions { + strokeWidth(1.0f) + strokeColor(Color.BLACK) + add(LatLng(1.0, 2.0)) + } + assertThat(polygonOptions.strokeWidth).isWithin(1e-6f).of(1.0f) + assertThat(polygonOptions.strokeColor).isEqualTo(Color.BLACK) + assertThat(polygonOptions.points).containsExactly(LatLng(1.0, 2.0)) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/PolylineOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/PolylineOptionsTest.kt new file mode 100644 index 000000000..5f1a17537 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/PolylineOptionsTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class PolylineOptionsTest { + + @Test + fun testBuilder() { + val polylineOptions = polylineOptions { + add(LatLng(1.0, 2.0)) + clickable(true) + color(0) + geodesic(true) + width(1f) + } + assertThat(polylineOptions.points).containsExactly(LatLng(1.0, 2.0)) + assertThat(polylineOptions.isClickable).isTrue() + assertThat(polylineOptions.color).isEqualTo(0) + assertThat(polylineOptions.isGeodesic).isTrue() + assertThat(polylineOptions.width).isWithin(1e-6f).of(1f) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/StreetViewPanoramaCameraTest.kt b/library/src/test/java/com/google/maps/android/model/StreetViewPanoramaCameraTest.kt new file mode 100644 index 000000000..261bac9f5 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/StreetViewPanoramaCameraTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class StreetViewPanoramaCameraTest { + + @Test + fun `test that StreetViewPanoramaCamera is constructed`() { + val camera = streetViewPanoramaCamera { + bearing(1f) + tilt(20f) + zoom(2f) + } + assertThat(camera.bearing).isWithin(1e-6f).of(1f) + assertThat(camera.tilt).isWithin(1e-6f).of(20f) + assertThat(camera.zoom).isWithin(1e-6f).of(2f) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/StreetViewPanoramaOrientationTest.kt b/library/src/test/java/com/google/maps/android/model/StreetViewPanoramaOrientationTest.kt new file mode 100644 index 000000000..1c3e05fd1 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/StreetViewPanoramaOrientationTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class StreetViewPanoramaOrientationTest { + + @Test + fun `test that StreetViewPanoramaOrientation is constructed`() { + val orientation = streetViewPanoramaOrientation { + bearing(1f) + tilt(20f) + } + assertThat(orientation.bearing).isWithin(1e-6f).of(1f) + assertThat(orientation.tilt).isWithin(1e-6f).of(20f) + } +} diff --git a/library/src/test/java/com/google/maps/android/model/TileOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/TileOverlayOptionsTest.kt new file mode 100644 index 000000000..c1e457495 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/model/TileOverlayOptionsTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.maps.android.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class TileOverlayOptionsTest { + + @Test + fun testBuilder() { + val tileOverlayOptions = tileOverlayOptions { + fadeIn(true) + transparency(0.5f) + visible(false) + zIndex(1f) + } + assertThat(tileOverlayOptions.fadeIn).isTrue() + assertThat(tileOverlayOptions.isVisible).isFalse() + assertThat(tileOverlayOptions.transparency).isWithin(1e-6f).of(0.5f) + assertThat(tileOverlayOptions.zIndex).isWithin(1e-6f).of(1f) + } +} diff --git a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt index 694ad56ce..a17f12181 100644 --- a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt +++ b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt @@ -16,46 +16,98 @@ package com.google.maps.android.utils.attribution import android.content.Context -import androidx.test.core.app.ApplicationProvider import com.google.android.gms.maps.MapsApiSettings +import com.google.common.truth.Truth.assertThat import com.google.maps.android.utils.meta.AttributionId -import io.mockk.every -import io.mockk.just -import io.mockk.mockkStatic -import io.mockk.runs -import io.mockk.unmockkStatic -import io.mockk.verify import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner -@RunWith(RobolectricTestRunner::class) +/** + * Unit test suite for [AttributionIdInitializer] and its deprecated KTX compatibility shim + * [com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer]. + * + * **Purpose:** + * Verifies that the Jetpack App Startup `Initializer` for library usage attribution correctly + * registers the library's unique attribution identifier (`AttributionId.VALUE` = "maps-utils-android") + * with the Google Maps SDK's [MapsApiSettings] upon application startup, and declares no dependent + * startup initializers. + * + * **How it works:** + * Uses Mockito's static mocking (`mockStatic(MapsApiSettings::class.java)`) to intercept calls + * to the static SDK method [MapsApiSettings.addInternalUsageAttributionId]. Each test instantiates + * an initializer, checks its dependencies, and invokes `create(context)`. + * + * **How we know it is correct:** + * - **Code under test:** Correct if invoking `create(context)` registers `AttributionId.VALUE` exactly + * once with the provided `Context`, and `dependencies()` returns an empty list. + * - **Test:** Correct because `mapsApiSettingsMock.verify { ... }` will fail the test if the static + * attribution registration method was not invoked or received incorrect parameters. + */ +@RunWith(MockitoJUnitRunner::class) class AttributionIdInitializerTest { + + @Mock + private lateinit var context: Context + + private lateinit var mapsApiSettingsMock: MockedStatic + @Before fun setUp() { - mockkStatic(MapsApiSettings::class) - every { MapsApiSettings.addInternalUsageAttributionId(any(), any()) } just runs + mapsApiSettingsMock = mockStatic(MapsApiSettings::class.java) } @After fun tearDown() { - unmockkStatic(MapsApiSettings::class) + mapsApiSettingsMock.close() } + /** + * **Purpose:** Tests that the canonical [AttributionIdInitializer] registers usage attribution + * and has no initializer dependencies. + * + * **How it works:** Instantiates [AttributionIdInitializer], asserts `dependencies()` is empty, + * calls `create(context)`, and verifies static invocation of [MapsApiSettings.addInternalUsageAttributionId]. + * + * **How we know it is correct:** + * - **Code under test:** Proves canonical initialization correctly passes `AttributionId.VALUE` to Maps SDK. + * - **Test:** Verifies exact static invocation arguments; fails if attribution registration is omitted or altered. + */ @Test - fun `create adds internal usage attribution id`() { - val context = ApplicationProvider.getApplicationContext() + fun `test canonical AttributionIdInitializer create and dependencies`() { val initializer = AttributionIdInitializer() - + assertThat(initializer.dependencies()).isEmpty() initializer.create(context) + mapsApiSettingsMock.verify { + MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE) + } + } - verify { - MapsApiSettings.addInternalUsageAttributionId( - context, - AttributionId.VALUE, - ) + /** + * **Purpose:** Tests that the deprecated KTX compatibility shim + * [com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer] continues to register + * usage attribution without breaking existing apps. + * + * **How it works:** Instantiates the KTX shim class and performs the exact same dependency + * and creation verification as the canonical initializer test. + * + * **How we know it is correct:** + * - **Code under test:** Proves backwards-compatible KTX typealias/shim preserves identical startup behavior. + * - **Test:** Fails if the KTX shim fails to invoke [MapsApiSettings.addInternalUsageAttributionId]. + */ + @Test + fun `test ktx AttributionIdInitializer create and dependencies`() { + val initializer = com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer() + assertThat(initializer.dependencies()).isEmpty() + initializer.create(context) + mapsApiSettingsMock.verify { + MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE) } } } From 01787a7c9a8fe84f426d58c29c0c546fdced3e5b Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:02:49 -0600 Subject: [PATCH 02/17] feat: port Maps SDK coroutine initialization extension from android-maps-ktx - Add canonical Context.awaitMapsSdkInitialized(preferredRenderer) suspending extension in com.google.maps.android. - Add deprecated backward-compatibility shim in com.google.maps.android.ktx. - Add canonical and shim unit test suites for MapsInitializer coroutine extensions. - Showcase awaitMapsSdkInitialized in KtxExtensionsDemoActivity and register demo in MainActivity. - Update README.md documentation with awaitMapsSdkInitialized usage example. --- README.md | 7 +- .../utils/demo/KtxExtensionsDemoActivity.kt | 9 +- .../maps/android/utils/demo/MainActivity.kt | 1 + .../google/maps/android/MapsInitializer.kt | 64 +++++++ .../maps/android/ktx/MapsInitializer.kt | 39 ++++ .../maps/android/MapsInitializerTest.kt | 171 ++++++++++++++++++ .../maps/android/ktx/MapsInitializerTest.kt | 118 ++++++++++++ 7 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 library/src/main/java/com/google/maps/android/MapsInitializer.kt create mode 100644 library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt create mode 100644 library/src/test/java/com/google/maps/android/MapsInitializerTest.kt create mode 100644 library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt diff --git a/README.md b/README.md index 99953fc82..d912ca46f 100644 --- a/README.md +++ b/README.md @@ -174,11 +174,16 @@ Full guides for using the utilities are published in All Kotlin extensions formerly provided by `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) are now integrated into `android-maps-utils` (`v6.0.0-rc01+`) under canonical packages (`com.google.maps.android.*`, `com.google.maps.android.clustering.*`, etc.). -#### 1. Coroutine Suspensions (`awaitMap()`, `awaitAnimateCamera()`) +#### 1. Coroutine Suspensions (`awaitMapsSdkInitialized()`, `awaitMap()`, `awaitAnimateCamera()`) ```kotlin +import com.google.android.gms.maps.MapsInitializer +import com.google.maps.android.awaitMapsSdkInitialized import com.google.maps.android.awaitMap import com.google.maps.android.awaitAnimateCamera +// Suspend until Maps SDK is initialized +val renderer: MapsInitializer.Renderer = context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + // Suspend until GoogleMap is ready on MapView / MapFragment val googleMap: GoogleMap = mapView.awaitMap() diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt index 4531561f5..3a14d6cca 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt @@ -35,10 +35,12 @@ import androidx.lifecycle.repeatOnLifecycle import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.MapsInitializer import com.google.android.gms.maps.model.LatLng import com.google.maps.android.addMarker import com.google.maps.android.awaitAnimateCamera import com.google.maps.android.awaitMap +import com.google.maps.android.awaitMapsSdkInitialized import com.google.maps.android.ktx.addCircle as deprecatedBridgeAddCircle import com.google.maps.android.mapClickEvents import kotlinx.coroutines.launch @@ -66,16 +68,19 @@ class KtxExtensionsDemoActivity : ComponentActivity() { val mapView = remember { MapView(context) } LaunchedEffect(mapView) { + // 1. Canonical awaitMapsSdkInitialized() coroutine suspension + context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + mapView.onCreate(Bundle()) mapView.onStart() mapView.onResume() - // 1. Canonical awaitMap() coroutine suspension + // 2. Canonical awaitMap() coroutine suspension val googleMap: GoogleMap = mapView.awaitMap() val sydney = LatLng(-33.852, 151.211) - // 2. Canonical addMarker builder DSL + // 3. Canonical addMarker builder DSL googleMap.addMarker { position(sydney) title("Sydney Opera House (Canonical Builder)") diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt index 2ed7856ff..9e67de3a6 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt @@ -133,6 +133,7 @@ class MainActivity : ComponentActivity() { Demo(R.string.demo_title_icon_generator, IconGeneratorDemoActivity::class.java), Demo(R.string.demo_title_tile_provider, TileProviderAndProjectionDemo::class.java), Demo(R.string.demo_title_animation_util, AnimationUtilDemoActivity::class.java), + Demo(R.string.demo_title_reactive_extensions, KtxExtensionsDemoActivity::class.java), ), ), DemoGroup( diff --git a/library/src/main/java/com/google/maps/android/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/MapsInitializer.kt new file mode 100644 index 000000000..77bfda674 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/MapsInitializer.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.maps.MapsInitializer +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +/** + * Suspends until the Google Maps SDK is initialized and returns the [MapsInitializer.Renderer] + * that was actually loaded. + * + * **Purpose:** + * Provides a modern coroutine suspension alternative to [MapsInitializer.initialize] with + * callback handlers, enabling clean, sequential asynchronous SDK initialization. + * + * **How it works:** + * Invokes [MapsInitializer.initialize] passing an [com.google.android.gms.maps.OnMapsSdkInitializedCallback]. + * When the callback fires, it resumes the coroutine with the loaded [MapsInitializer.Renderer]. + * If the SDK returns an error status code other than [ConnectionResult.SUCCESS] and does not + * invoke the callback, it resumes with [GooglePlayServicesNotAvailableException] holding the error code. + * + * **Cancellation:** + * This suspending function does not support cancellation because the underlying Maps SDK + * [MapsInitializer.initialize] operation cannot be cancelled once initiated. Only the first + * Maps SDK initialization in an application lifecycle honors [preferredRenderer]; passing `null` + * uses the SDK's default preference. + * + * @param preferredRenderer the renderer to request, or `null` to use the SDK default preference + * @return the [MapsInitializer.Renderer] actually loaded by the Maps SDK + * @throws GooglePlayServicesNotAvailableException if initialization returns a status other than + * [ConnectionResult.SUCCESS] without invoking the callback + */ +public suspend inline fun Context.awaitMapsSdkInitialized( + preferredRenderer: MapsInitializer.Renderer? = null +): MapsInitializer.Renderer = + suspendCoroutine { continuation -> + var callbackInvoked = false + val status = MapsInitializer.initialize(this, preferredRenderer) { renderer -> + callbackInvoked = true + continuation.resume(renderer) + } + if (!callbackInvoked && status != ConnectionResult.SUCCESS) { + continuation.resumeWithException(GooglePlayServicesNotAvailableException(status)) + } + } diff --git a/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt new file mode 100644 index 000000000..24e93a530 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt @@ -0,0 +1,39 @@ +@file:Suppress("NOTHING_TO_INLINE") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.ktx + +import android.content.Context +import com.google.android.gms.maps.MapsInitializer +import com.google.maps.android.awaitMapsSdkInitialized as canonicalAwaitMapsSdkInitialized + +/** + * Suspends until the Google Maps SDK is initialized and returns the [MapsInitializer.Renderer] + * that was actually loaded. + * + * @deprecated Use [com.google.maps.android.awaitMapsSdkInitialized] instead. + */ +@Deprecated( + message = "Use com.google.maps.android.awaitMapsSdkInitialized instead", + replaceWith = ReplaceWith( + "awaitMapsSdkInitialized(preferredRenderer)", + "com.google.maps.android.awaitMapsSdkInitialized" + ), + level = DeprecationLevel.WARNING +) +public suspend inline fun Context.awaitMapsSdkInitialized( + preferredRenderer: MapsInitializer.Renderer? = null +): MapsInitializer.Renderer = this.canonicalAwaitMapsSdkInitialized(preferredRenderer) diff --git a/library/src/test/java/com/google/maps/android/MapsInitializerTest.kt b/library/src/test/java/com/google/maps/android/MapsInitializerTest.kt new file mode 100644 index 000000000..ea7757440 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/MapsInitializerTest.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.maps.MapsInitializer +import com.google.android.gms.maps.OnMapsSdkInitializedCallback +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito.mockStatic +import org.mockito.junit.MockitoJUnitRunner + +/** + * Unit test suite for canonical Maps SDK coroutine initialization extension [Context.awaitMapsSdkInitialized]. + * + * **Purpose:** + * Validates that [Context.awaitMapsSdkInitialized] properly bridges [MapsInitializer.initialize] + * callbacks into coroutine resumption with the loaded [MapsInitializer.Renderer], handles `null` + * and default preferences, and converts initialization failure status codes into + * [GooglePlayServicesNotAvailableException]. + * + * **How it works:** + * Uses Mockito static mocking (`mockStatic(MapsInitializer::class.java)`) to intercept static calls + * to [MapsInitializer.initialize] on the provided mock [Context]. Test methods trigger either the + * asynchronous callback or return failure status codes and verify the returned result or thrown exception. + * + * **How we know it is correct:** + * - **Success cases:** Asserts the returned renderer matches the value supplied to [OnMapsSdkInitializedCallback]. + * - **Error cases:** Asserts [GooglePlayServicesNotAvailableException] is thrown with the exact [ConnectionResult] error code. + */ +@RunWith(MockitoJUnitRunner::class) +public class MapsInitializerTest { + + @Mock + private lateinit var context: Context + + private lateinit var mapsInitializerMock: MockedStatic + + @Before + public fun setUp() { + mapsInitializerMock = mockStatic(MapsInitializer::class.java) + } + + @After + public fun tearDown() { + mapsInitializerMock.close() + } + + /** + * **Purpose:** Verifies [Context.awaitMapsSdkInitialized] resumes with the loaded renderer when initialization succeeds. + * **How it works:** Mocks [MapsInitializer.initialize] to invoke callback with [MapsInitializer.Renderer.LEGACY] and return [ConnectionResult.SUCCESS]. + * **How we know it is correct:** Asserts the returned renderer equals [MapsInitializer.Renderer.LEGACY]. + */ + @Suppress("DEPRECATION") + @Test + public fun testAwaitMapsSdkInitializedReturnsActualRenderer(): Unit = runTest { + mapsInitializerMock.`when` { + MapsInitializer.initialize( + eq(context), + eq(MapsInitializer.Renderer.LATEST), + any(OnMapsSdkInitializedCallback::class.java) + ) + }.thenAnswer { invocation -> + invocation.getArgument(2) + .onMapsSdkInitialized(MapsInitializer.Renderer.LEGACY) + ConnectionResult.SUCCESS + } + + val renderer = context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + + assertThat(renderer).isEqualTo(MapsInitializer.Renderer.LEGACY) + } + + /** + * **Purpose:** Verifies [Context.awaitMapsSdkInitialized] throws [GooglePlayServicesNotAvailableException] on initialization failure. + * **How it works:** Mocks [MapsInitializer.initialize] to return [ConnectionResult.SERVICE_MISSING] without triggering the callback. + * **How we know it is correct:** Asserts [GooglePlayServicesNotAvailableException] is thrown with [ConnectionResult.SERVICE_MISSING] error code. + */ + @Test + public fun testAwaitMapsSdkInitializedThrowsForNonSuccessStatus(): Unit = runTest { + mapsInitializerMock.`when` { + MapsInitializer.initialize( + eq(context), + eq(MapsInitializer.Renderer.LATEST), + any(OnMapsSdkInitializedCallback::class.java) + ) + }.thenReturn(ConnectionResult.SERVICE_MISSING) + + val exception = runCatching { + context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + }.exceptionOrNull() + + assertThat(exception).isInstanceOf(GooglePlayServicesNotAvailableException::class.java) + assertThat((exception as GooglePlayServicesNotAvailableException).errorCode) + .isEqualTo(ConnectionResult.SERVICE_MISSING) + } + + /** + * **Purpose:** Verifies [Context.awaitMapsSdkInitialized] handles an explicit `null` preferred renderer parameter. + * **How it works:** Mocks [MapsInitializer.initialize] with `null` preferred renderer and invokes callback with [MapsInitializer.Renderer.LATEST]. + * **How we know it is correct:** Asserts the returned renderer equals [MapsInitializer.Renderer.LATEST]. + */ + @Suppress("DEPRECATION") + @Test + public fun testAwaitMapsSdkInitializedWithNullPreferredRenderer(): Unit = runTest { + mapsInitializerMock.`when` { + MapsInitializer.initialize( + eq(context), + eq(null), + any(OnMapsSdkInitializedCallback::class.java) + ) + }.thenAnswer { invocation -> + invocation.getArgument(2) + .onMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + ConnectionResult.SUCCESS + } + + val renderer = context.awaitMapsSdkInitialized(null) + + assertThat(renderer).isEqualTo(MapsInitializer.Renderer.LATEST) + } + + /** + * **Purpose:** Verifies [Context.awaitMapsSdkInitialized] defaults to `null` preferred renderer when called without arguments. + * **How it works:** Calls `context.awaitMapsSdkInitialized()` with default argument and verifies callback resolution. + * **How we know it is correct:** Asserts the returned renderer equals [MapsInitializer.Renderer.LATEST]. + */ + @Suppress("DEPRECATION") + @Test + public fun testAwaitMapsSdkInitializedWithDefaultNullRenderer(): Unit = runTest { + mapsInitializerMock.`when` { + MapsInitializer.initialize( + eq(context), + eq(null), + any(OnMapsSdkInitializedCallback::class.java) + ) + }.thenAnswer { invocation -> + invocation.getArgument(2) + .onMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + ConnectionResult.SUCCESS + } + + val renderer = context.awaitMapsSdkInitialized() + + assertThat(renderer).isEqualTo(MapsInitializer.Renderer.LATEST) + } +} diff --git a/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt b/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt new file mode 100644 index 000000000..ab063a7c3 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt @@ -0,0 +1,118 @@ +@file:Suppress("DEPRECATION") +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.ktx + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.maps.MapsInitializer +import com.google.android.gms.maps.OnMapsSdkInitializedCallback +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito.mockStatic +import org.mockito.junit.MockitoJUnitRunner + +/** + * Unit test suite for the backward-compatibility KTX shim [com.google.maps.android.ktx.awaitMapsSdkInitialized]. + * + * **Purpose:** + * Validates that the deprecated KTX shim [com.google.maps.android.ktx.awaitMapsSdkInitialized] forwards + * seamlessly to the canonical [com.google.maps.android.awaitMapsSdkInitialized] implementation. + * + * **How it works:** + * Uses Mockito static mocking (`mockStatic(MapsInitializer::class.java)`) to intercept SDK calls and + * verifies that the deprecated KTX extension correctly returns loaded renderers and propagates exceptions. + * + * **How we know it is correct:** + * - Verifies return value parity with canonical implementation. + * - Verifies exception propagation matches canonical implementation. + */ +@RunWith(MockitoJUnitRunner::class) +public class MapsInitializerTest { + + @Mock + private lateinit var context: Context + + private lateinit var mapsInitializerMock: MockedStatic + + @Before + public fun setUp() { + mapsInitializerMock = mockStatic(MapsInitializer::class.java) + } + + @After + public fun tearDown() { + mapsInitializerMock.close() + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitMapsSdkInitialized` returns the loaded renderer. + * **How it works:** Calls [com.google.maps.android.ktx.awaitMapsSdkInitialized] and simulates successful SDK initialization. + * **How we know it is correct:** Asserts returned renderer equals [MapsInitializer.Renderer.LATEST]. + */ + @Test + public fun testKtxAwaitMapsSdkInitializedReturnsActualRenderer(): Unit = runTest { + mapsInitializerMock.`when` { + MapsInitializer.initialize( + eq(context), + eq(MapsInitializer.Renderer.LATEST), + any(OnMapsSdkInitializedCallback::class.java) + ) + }.thenAnswer { invocation -> + invocation.getArgument(2) + .onMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + ConnectionResult.SUCCESS + } + + val renderer = context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + + assertThat(renderer).isEqualTo(MapsInitializer.Renderer.LATEST) + } + + /** + * **Purpose:** Verifies deprecated KTX shim `awaitMapsSdkInitialized` propagates initialization exceptions. + * **How it works:** Simulates SDK missing error and calls [com.google.maps.android.ktx.awaitMapsSdkInitialized]. + * **How we know it is correct:** Asserts [GooglePlayServicesNotAvailableException] is thrown with [ConnectionResult.SERVICE_MISSING]. + */ + @Test + public fun testKtxAwaitMapsSdkInitializedThrowsForNonSuccessStatus(): Unit = runTest { + mapsInitializerMock.`when` { + MapsInitializer.initialize( + eq(context), + eq(MapsInitializer.Renderer.LATEST), + any(OnMapsSdkInitializedCallback::class.java) + ) + }.thenReturn(ConnectionResult.SERVICE_MISSING) + + val exception = runCatching { + context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + }.exceptionOrNull() + + assertThat(exception).isInstanceOf(GooglePlayServicesNotAvailableException::class.java) + assertThat((exception as GooglePlayServicesNotAvailableException).errorCode) + .isEqualTo(ConnectionResult.SERVICE_MISSING) + } +} From 672afc432b883162f74f909ae3837700e971bc8a Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:37:39 +0000 Subject: [PATCH 03/17] chore: release v6.0.0-rc01 --- .gemini/skills/android-maps-utils/SKILL.md | 2 +- .release-please-manifest.json | 2 +- MIGRATION.md | 2 +- build.gradle.kts | 2 +- llm-integration-prompt.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index 1c3227120..4d68e0895 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:5.2.0") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc01") // x-release-please-version } ``` diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 474bbb2c5..4fd617e67 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.2.0" + ".": "6.0.0-rc01" } \ No newline at end of file diff --git a/MIGRATION.md b/MIGRATION.md index 1deaca3a6..6cfca422f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,7 @@ If your project uses all utilities, you can import the aggregator artifact direc ```toml # gradle/libs.versions.toml [versions] -androidMapsUtils = "5.2.0" # x-release-please-version +androidMapsUtils = "6.0.0-rc01" # x-release-please-version [libraries] android-maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "androidMapsUtils" } diff --git a/build.gradle.kts b/build.gradle.kts index 6f3aa73a3..01306083f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ tasks.register("installAndLaunch") { allprojects { group = "com.google.maps.android" // {x-release-please-start-version} - version = "5.2.0" + version = "6.0.0-rc01" // {x-release-please-end} plugins.withId("java") { diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index 1c3227120..4d68e0895 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:5.2.0") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc01") // x-release-please-version } ``` From 013f8fec6b88ab78bffd7d781df2d26df7717dc4 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:24:42 -0600 Subject: [PATCH 04/17] chore: release v6.0.0-rc02 --- .gemini/skills/android-maps-utils/SKILL.md | 2 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ MIGRATION.md | 2 +- build.gradle.kts | 2 +- gradle.properties | 2 +- llm-integration-prompt.md | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index 4d68e0895..3cfe73a1d 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc01") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc02") // x-release-please-version } ``` diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4fd617e67..25898d28c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.0.0-rc01" + ".": "6.0.0-rc02" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cab0cd7c9..99833b427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [6.0.0-rc02](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc01...v6.0.0-rc02) (2026-08-28) + +### Chores + +* Rebase on main branch +* Make GPG publication signing conditional for local maven publishing +* Update dependencies and Gradle wrapper + ## [6.0.0-rc01](https://github.com/googlemaps/android-maps-utils/compare/v5.2.0...v6.0.0-rc01) (2026-07-13) ### Features & Breaking Changes (KTX Consolidation) diff --git a/MIGRATION.md b/MIGRATION.md index 6cfca422f..831d24c04 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,7 @@ If your project uses all utilities, you can import the aggregator artifact direc ```toml # gradle/libs.versions.toml [versions] -androidMapsUtils = "6.0.0-rc01" # x-release-please-version +androidMapsUtils = "6.0.0-rc02" # x-release-please-version [libraries] android-maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "androidMapsUtils" } diff --git a/build.gradle.kts b/build.gradle.kts index 01306083f..a6a90a98b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ tasks.register("installAndLaunch") { allprojects { group = "com.google.maps.android" // {x-release-please-start-version} - version = "6.0.0-rc01" + version = "6.0.0-rc02" // {x-release-please-end} plugins.withId("java") { diff --git a/gradle.properties b/gradle.properties index 1bbc7354e..9d4d9e243 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,6 +39,6 @@ mavenCentralPassword= # If true, publishToMavenCentral will also close and release the staging repository mavenCentralAutomaticRelease=false -VERSION_NAME=6.0.0-rc01 +VERSION_NAME=6.0.0-rc02 GROUP=com.google.maps.android diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index 4d68e0895..3cfe73a1d 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc01") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc02") // x-release-please-version } ``` From a6f6f73415e251fe51486876098b55b510411ae7 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:48:22 -0600 Subject: [PATCH 05/17] fix: resolve lint test SDK requirement and configure isolated test home for unit tests --- build.gradle.kts | 16 ++++++++++++++++ .../com/google/maps/android/ktx/GoogleMap.kt | 2 ++ .../android/lint/checks/GoogleMapDetectorTest.kt | 3 +++ 3 files changed, 21 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index a6a90a98b..dcabd201e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,4 +59,20 @@ allprojects { } } } + + tasks.withType().configureEach { + val testHome = rootProject.layout.buildDirectory.dir("test-home").get().asFile + testHome.mkdirs() + val m2Link = File(testHome, ".m2") + if (!m2Link.exists()) { + val realM2 = File(System.getProperty("user.home"), ".m2") + if (realM2.exists()) { + try { + java.nio.file.Files.createSymbolicLink(m2Link.toPath(), realM2.toPath()) + } catch (_: Exception) {} + } + } + systemProperty("user.home", testHome.absolutePath) + environment("ANDROID_HOME", System.getenv("ANDROID_HOME") ?: "/usr/local/google/home/dkhawk/Android/Sdk") + } } diff --git a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt index 564cc577f..2162754b9 100644 --- a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt @@ -139,6 +139,7 @@ public fun GoogleMap.circleClickEvents(): Flow = this.canonicalCircleCli @Deprecated("Moved to com.google.maps.android.groundOverlayClicks", ReplaceWith("groundOverlayClicks()", "com.google.maps.android.groundOverlayClicks")) public fun GoogleMap.groundOverlayClicks(): Flow = this.canonicalGroundOverlayClicks() +@Suppress("DEPRECATION") @Deprecated("Moved to com.google.maps.android.indoorStateChangeEvents", ReplaceWith("indoorStateChangeEvents()", "com.google.maps.android.indoorStateChangeEvents")) public fun GoogleMap.indoorStateChangeEvents(): Flow = this.canonicalIndoorStateChangeEvents() @@ -160,6 +161,7 @@ public fun GoogleMap.mapLongClickEvents(): Flow = this.canonicalMapLongC @Deprecated("Moved to com.google.maps.android.markerClickEvents", ReplaceWith("markerClickEvents()", "com.google.maps.android.markerClickEvents")) public fun GoogleMap.markerClickEvents(): Flow = this.canonicalMarkerClickEvents() +@Suppress("DEPRECATION") @Deprecated("Moved to com.google.maps.android.markerDragEvents", ReplaceWith("markerDragEvents()", "com.google.maps.android.markerDragEvents")) public fun GoogleMap.markerDragEvents(): Flow = this.canonicalMarkerDragEvents() diff --git a/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt b/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt index db422fb57..00b4b1ca6 100644 --- a/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt +++ b/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt @@ -23,6 +23,9 @@ import com.android.tools.lint.detector.api.TextFormat @Suppress("UnstableApiUsage") class GoogleMapDetectorTest : LintDetectorTest() { + override fun lint(): com.android.tools.lint.checks.infrastructure.TestLintTask = + super.lint().allowMissingSdk() + fun testSetOnMarkerDragListener() { lint() .files( From 1ad43b806affc23a89907b3cb91bba8afa068ae4 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:49:43 -0600 Subject: [PATCH 06/17] chore: release v6.0.0-rc03 --- .gemini/skills/android-maps-utils/SKILL.md | 2 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ MIGRATION.md | 2 +- build.gradle.kts | 2 +- gradle.properties | 2 +- llm-integration-prompt.md | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index 3cfe73a1d..8b8494a9e 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc02") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc03") // x-release-please-version } ``` diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 25898d28c..483009292 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.0.0-rc02" + ".": "6.0.0-rc03" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 99833b427..c8a9140e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [6.0.0-rc03](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc02...v6.0.0-rc03) (2026-08-31) + +### Bug Fixes + +* Resolve lint detector test Android SDK requirement with `allowMissingSdk()` +* Configure isolated test home directory for unit test execution in restricted sandboxes +* Suppress internal deprecation warnings in legacy KTX compatibility shims + ## [6.0.0-rc02](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc01...v6.0.0-rc02) (2026-08-28) ### Chores diff --git a/MIGRATION.md b/MIGRATION.md index 831d24c04..2f68288ca 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,7 @@ If your project uses all utilities, you can import the aggregator artifact direc ```toml # gradle/libs.versions.toml [versions] -androidMapsUtils = "6.0.0-rc02" # x-release-please-version +androidMapsUtils = "6.0.0-rc03" # x-release-please-version [libraries] android-maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "androidMapsUtils" } diff --git a/build.gradle.kts b/build.gradle.kts index dcabd201e..0c23cec59 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ tasks.register("installAndLaunch") { allprojects { group = "com.google.maps.android" // {x-release-please-start-version} - version = "6.0.0-rc02" + version = "6.0.0-rc03" // {x-release-please-end} plugins.withId("java") { diff --git a/gradle.properties b/gradle.properties index 9d4d9e243..53363d32a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,6 +39,6 @@ mavenCentralPassword= # If true, publishToMavenCentral will also close and release the staging repository mavenCentralAutomaticRelease=false -VERSION_NAME=6.0.0-rc02 +VERSION_NAME=6.0.0-rc03 GROUP=com.google.maps.android diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index 3cfe73a1d..8b8494a9e 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc02") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc03") // x-release-please-version } ``` From 7ccd8af633a9940a31145f77e3c9f21e1fed735b Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:41:43 -0600 Subject: [PATCH 07/17] chore: prepare v6.0.0 final release --- .gemini/skills/android-maps-utils/SKILL.md | 2 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ MIGRATION.md | 2 +- README.md | 6 +++--- build.gradle.kts | 2 +- gradle.properties | 2 +- llm-integration-prompt.md | 2 +- release-please-config.json | 2 -- 9 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index 8b8494a9e..317cda4ac 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc03") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0") // x-release-please-version } ``` diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 483009292..d2cfbb5cf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.0.0-rc03" + ".": "6.0.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a9140e6..810cb5693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [6.0.0](https://github.com/googlemaps/android-maps-utils/compare/v5.2.0...v6.0.0) (2026-09-14) + +### Features & Breaking Changes (KTX Consolidation) + +* **Consolidated Kotlin Extensions (KTX into Utils)**: Moved all functionality from `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) directly into `android-maps-utils`. + * **Canonical Non-KTX Packages**: All reactive Coroutine/Flow extensions (`awaitMap()`, `mapClickEvents()`, `cameraMoveEvents()`) and option builder DSLs (`addMarker`, `addPolyline`, `addPolygon`) now reside in canonical `android-maps-utils` packages (`com.google.maps.android.*`, `com.google.maps.android.model.*`, `com.google.maps.android.collections.*`, `com.google.maps.android.clustering.*`, `com.google.maps.android.data.geojson.*`, `com.google.maps.android.data.kml.*`, `com.google.maps.android.heatmaps.*`). + * **Deprecated Compatibility Layer**: To ensure full backward compatibility for existing codebases, the legacy `com.google.maps.android.ktx.*` package structure has been preserved with `@Deprecated(level = DeprecationLevel.WARNING, replaceWith = ReplaceWith(...))` forwarding wrappers and typealiases. Existing imports will continue to compile seamlessly with deprecation warnings pointing to the canonical replacements. + * **Demo & Test Consolidation**: Integrated full reactive extension demo (`KtxExtensionsDemoActivity`) into the `:demo` app and migrated all 18 unit test suites from `android-maps-ktx` with both canonical and shim test coverage. + ## [6.0.0-rc03](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc02...v6.0.0-rc03) (2026-08-31) ### Bug Fixes diff --git a/MIGRATION.md b/MIGRATION.md index 2f68288ca..a5a7c59ed 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,7 @@ If your project uses all utilities, you can import the aggregator artifact direc ```toml # gradle/libs.versions.toml [versions] -androidMapsUtils = "6.0.0-rc03" # x-release-please-version +androidMapsUtils = "6.0.0" # x-release-please-version [libraries] android-maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "androidMapsUtils" } diff --git a/README.md b/README.md index d912ca46f..7334026f4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ upgrading from 4.x, see the [migration guide](MIGRATION.md). - **Reactive Kotlin Extensions & Builders** — coroutine suspensions (`awaitMap()`), reactive `Flow` observers (`mapClickEvents()`), and option builder DSLs (`addMarker { ... }`) consolidated directly into `com.google.maps.android.*`. > [!IMPORTANT] -> **KTX Consolidation Notice (`v6.0.0-rc01+`)**: All Kotlin extensions (`maps-ktx` and `maps-utils-ktx` from `android-maps-ktx`) are now built directly into `android-maps-utils` under the canonical `com.google.maps.android.*` packages. Separate dependencies on `android-maps-ktx` or `maps-utils-ktx` are no longer needed and should be removed. Legacy calls to `com.google.maps.android.ktx.*` packages remain supported via `@Deprecated(level = DeprecationLevel.WARNING)` bridges that forward directly to canonical implementations. +> **KTX Consolidation Notice (`v6.0.0+`)**: All Kotlin extensions (`maps-ktx` and `maps-utils-ktx` from `android-maps-ktx`) are now built directly into `android-maps-utils` under the canonical `com.google.maps.android.*` packages. Separate dependencies on `android-maps-ktx` or `maps-utils-ktx` are no longer needed and should be removed. Legacy calls to `com.google.maps.android.ktx.*` packages remain supported via `@Deprecated(level = DeprecationLevel.WARNING)` bridges that forward directly to canonical implementations.

@@ -168,11 +168,11 @@ Full guides for using the utilities are published in
- Reactive Kotlin Extensions & Builders (Consolidated in v6.0.0-rc01) + Reactive Kotlin Extensions & Builders (Consolidated in v6.0.0) ### Reactive Kotlin Extensions & Builders -All Kotlin extensions formerly provided by `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) are now integrated into `android-maps-utils` (`v6.0.0-rc01+`) under canonical packages (`com.google.maps.android.*`, `com.google.maps.android.clustering.*`, etc.). +All Kotlin extensions formerly provided by `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) are now integrated into `android-maps-utils` (`v6.0.0+`) under canonical packages (`com.google.maps.android.*`, `com.google.maps.android.clustering.*`, etc.). #### 1. Coroutine Suspensions (`awaitMapsSdkInitialized()`, `awaitMap()`, `awaitAnimateCamera()`) ```kotlin diff --git a/build.gradle.kts b/build.gradle.kts index 0c23cec59..4ce60ca33 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ tasks.register("installAndLaunch") { allprojects { group = "com.google.maps.android" // {x-release-please-start-version} - version = "6.0.0-rc03" + version = "6.0.0" // {x-release-please-end} plugins.withId("java") { diff --git a/gradle.properties b/gradle.properties index 53363d32a..52d8baf81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,6 +39,6 @@ mavenCentralPassword= # If true, publishToMavenCentral will also close and release the staging repository mavenCentralAutomaticRelease=false -VERSION_NAME=6.0.0-rc03 +VERSION_NAME=6.0.0 GROUP=com.google.maps.android diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index 8b8494a9e..317cda4ac 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc03") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0") // x-release-please-version } ``` diff --git a/release-please-config.json b/release-please-config.json index 4a6a2ac02..e0f2c1024 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,8 +3,6 @@ "packages": { ".": { "release-type": "simple", - "prerelease": true, - "prerelease-type": "rc", "extra-files": [ "README.md", "build.gradle.kts", From cc0692344e0f463e42bcf3417e18e964cfa425eb Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:20:59 -0600 Subject: [PATCH 08/17] build(deps): update dependencies for v6.0.0 release --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6ce64d133..1ba2cdaf6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ compose-bom = "2026.09.00" # --- Google Services (Maps) --- # Versions for Google Play Services libraries essential for map functionality. play-services-maps = "20.0.0" -play-services-location = "21.3.0" +play-services-location = "21.4.0" navigation-sdk = "7.9.0" desugar-jdk-libs = "2.1.5" @@ -53,12 +53,12 @@ uiautomator = "2.4.0" # --- Lint & Analysis --- # Versions for code quality and static analysis tools. kover = "0.9.9" -lint = "32.3.1" +lint = "32.4.0" # --- Gradle Plugins --- # Versions for Gradle plugins used in the build process. dokka-gradle-plugin = "2.2.0" -gradle = "9.3.1" +gradle = "9.4.0" gradleMavenPublishPlugin = "0.37.0" secrets-gradle-plugin = "2.0.1" serialization = "1.0.2.1" From 31aac92b28a73342af93ecf3711316efeaa0020c Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:53:36 -0600 Subject: [PATCH 09/17] refactor: address review comments on coroutines, inlining, and file structure --- .vscode/settings.json | 4 - .../android/clustering/ClusterManagerFlows.kt | 10 ++ .../maps/android/geometry/PointExtensions.kt | 5 +- .../maps/android/ktx/utils/geometry/Point.kt | 5 +- .../utils/clustering/ClusterManagerTest.kt | 2 +- .../android/ktx/utils/geometry/PointTest.kt | 2 +- .../maps/android/data/geojson/GeoJson.kt | 5 +- .../com/google/maps/android/data/kml/Kml.kt | 5 +- .../maps/android/ktx/utils/geojson/GeoJson.kt | 6 +- .../google/maps/android/ktx/utils/kml/Kml.kt | 6 +- .../google/maps/android/heatmaps/Heatmap.kt | 7 +- .../android/ktx/utils/heatmaps/Heatmap.kt | 8 +- .../android/ktx/utils/heatmaps/HeatmapTest.kt | 2 +- .../java/com/google/maps/android/GoogleMap.kt | 9 +- .../java/com/google/maps/android/LatLng.kt | 36 ++++--- .../java/com/google/maps/android/MapView.kt | 6 +- .../google/maps/android/MapsInitializer.kt | 8 +- .../java/com/google/maps/android/Polygon.kt | 9 +- .../java/com/google/maps/android/Polyline.kt | 5 +- .../android/StreetViewPanoramaFragment.kt | 6 +- .../maps/android/StreetViewPanoramaView.kt | 6 +- .../SupportStreetViewPanoramaFragment.kt | 6 +- .../maps/android/collections/CircleManager.kt | 31 ------- .../android/collections/CircleManagerFlows.kt | 55 +++++++++++ .../collections/GroundOverlayManager.kt | 33 ------- .../collections/GroundOverlayManagerFlows.kt | 57 ++++++++++++ .../maps/android/collections/MarkerManager.kt | 69 -------------- .../android/collections/MarkerManagerFlows.kt | 93 +++++++++++++++++++ .../android/collections/PolygonManager.kt | 33 ------- .../collections/PolygonManagerFlows.kt | 57 ++++++++++++ .../android/collections/PolylineManager.kt | 33 ------- .../collections/PolylineManagerFlows.kt | 57 ++++++++++++ .../com/google/maps/android/ktx/GoogleMap.kt | 9 +- .../google/maps/android/ktx/MapFragment.kt | 4 +- .../com/google/maps/android/ktx/MapView.kt | 4 +- .../maps/android/ktx/MapsInitializer.kt | 4 +- .../android/ktx/StreetViewPanoramaFragment.kt | 4 +- .../android/ktx/StreetViewPanoramaView.kt | 4 +- .../maps/android/ktx/SupportMapFragment.kt | 4 +- .../ktx/SupportStreetViewPanoramaFragment.kt | 4 +- .../maps/android/ktx/model/CameraPosition.kt | 8 +- .../maps/android/ktx/model/CircleOptions.kt | 8 +- .../android/ktx/model/GroundOverlayOptions.kt | 8 +- .../maps/android/ktx/model/MarkerOptions.kt | 8 +- .../maps/android/ktx/model/PolygonOptions.kt | 8 +- .../maps/android/ktx/model/PolylineOptions.kt | 8 +- .../ktx/model/StreetViewPanoramaCamera.kt | 8 +- .../model/StreetViewPanoramaOrientation.kt | 8 +- .../android/ktx/model/TileOverlayOptions.kt | 8 +- .../google/maps/android/ktx/utils/LatLng.kt | 36 +++---- .../google/maps/android/ktx/utils/Polygon.kt | 10 +- .../google/maps/android/ktx/utils/Polyline.kt | 6 +- .../ktx/utils/collection/CircleManager.kt | 7 +- .../utils/collection/GroundOverlayManager.kt | 8 +- .../ktx/utils/collection/MarkerManager.kt | 7 +- .../ktx/utils/collection/PolygonManager.kt | 7 +- .../ktx/utils/collection/PolylineManager.kt | 7 +- .../android/location/FusedLocationProvider.kt | 2 +- .../maps/android/location/LocationManager.kt | 9 +- .../com/google/maps/android/GoogleMapTest.kt | 50 +++++----- .../google/maps/android/ktx/GoogleMapTest.kt | 56 +++++------ .../maps/android/ktx/MapsInitializerTest.kt | 2 +- .../android/ktx/model/CameraPositionTest.kt | 2 +- .../android/ktx/model/CircleOptionsTest.kt | 2 +- .../ktx/model/GroundOverlayOptionsTest.kt | 2 +- .../android/ktx/model/MarkerOptionsTest.kt | 2 +- .../android/ktx/model/PolygonOptionsTest.kt | 2 +- .../android/ktx/model/PolylineOptionsTest.kt | 2 +- .../ktx/model/StreetViewPanoramaCameraTest.kt | 2 +- .../StreetViewPanoramaOrientationTest.kt | 2 +- .../ktx/model/TileOverlayOptionsTest.kt | 2 +- .../maps/android/ktx/utils/LatLngTest.kt | 2 +- .../maps/android/ktx/utils/PolygonTest.kt | 2 +- .../maps/android/ktx/utils/PolylineTest.kt | 2 +- .../collection/CollectionManagersTest.kt | 2 +- .../location/FusedLocationProviderTest.kt | 2 +- .../ktx/utils/location/LocationManagerTest.kt | 2 +- 77 files changed, 579 insertions(+), 443 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 library/src/main/java/com/google/maps/android/collections/CircleManagerFlows.kt create mode 100644 library/src/main/java/com/google/maps/android/collections/GroundOverlayManagerFlows.kt create mode 100644 library/src/main/java/com/google/maps/android/collections/MarkerManagerFlows.kt create mode 100644 library/src/main/java/com/google/maps/android/collections/PolygonManagerFlows.kt create mode 100644 library/src/main/java/com/google/maps/android/collections/PolylineManagerFlows.kt diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e01206508..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "java.compile.nullAnalysis.mode": "automatic", - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file diff --git a/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt index c98819306..51fd21d4e 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt @@ -32,6 +32,11 @@ import kotlinx.coroutines.flow.callbackFlow * multiple collectors will result in listener hijacking, and cancelling any observer will unregister * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) * for multi-observer configurations. + * + * **Note on event consumption**: The underlying SDK listener returns the result of `trySend().isSuccess`. + * When an emission is accepted by the flow buffer, the click event is considered consumed (`true`), + * suppressing default SDK behavior (such as zooming). Under backpressure if the buffer is full, + * `trySend` returns `false`, allowing default SDK click handling to proceed. */ public fun ClusterManager.clusterClickEvents(): Flow> = callbackFlow { @@ -51,6 +56,11 @@ public fun ClusterManager.clusterClickEvents(): Flow ClusterManager.clusterItemClickEvents(): Flow = callbackFlow { diff --git a/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt b/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt index 147faea33..211f253fa 100644 --- a/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt +++ b/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt @@ -15,7 +15,6 @@ * */ -@file:Suppress("NOTHING_TO_INLINE") package com.google.maps.android.geometry import com.google.maps.android.geometry.Point @@ -29,7 +28,7 @@ import com.google.maps.android.geometry.Point * val (x, _) = point * ``` */ -public inline operator fun Point.component1(): Double = this.x +public operator fun Point.component1(): Double = this.x /** * Returns the y value of this Point. @@ -39,4 +38,4 @@ public inline operator fun Point.component1(): Double = this.x * ``` * val (_, y) = point */ -public inline operator fun Point.component2(): Double = this.y +public operator fun Point.component2(): Double = this.y diff --git a/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt b/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt index 6741a5750..53283208d 100644 --- a/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt +++ b/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -21,7 +20,7 @@ import com.google.maps.android.geometry.component1 as canonicalComponent1 import com.google.maps.android.geometry.component2 as canonicalComponent2 @Deprecated("Moved to com.google.maps.android.geometry.component1", ReplaceWith("component1()", "com.google.maps.android.geometry.component1")) -public inline operator fun Point.component1(): Double = this.canonicalComponent1() +public operator fun Point.component1(): Double = this.canonicalComponent1() @Deprecated("Moved to com.google.maps.android.geometry.component2", ReplaceWith("component2()", "com.google.maps.android.geometry.component2")) -public inline operator fun Point.component2(): Double = this.canonicalComponent2() +public operator fun Point.component2(): Double = this.canonicalComponent2() diff --git a/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt index 3f24783fa..4952e8e86 100644 --- a/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.clustering diff --git a/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt b/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt index 55bc151f7..d4e402ed3 100644 --- a/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.geometry diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt index 16a56a42a..3619a0b1e 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt @@ -15,7 +15,6 @@ * */ -@file:Suppress("NOTHING_TO_INLINE") package com.google.maps.android.data.geojson import android.content.Context @@ -33,7 +32,7 @@ import org.json.JSONObject * Alias for the [GeoJsonLayer] constructor that provides Kotlin named parameters and default * values. */ -public inline fun geoJsonLayer( +public fun geoJsonLayer( map: GoogleMap, geoJsonFile: JSONObject, markerManager: MarkerManager? = null, @@ -53,7 +52,7 @@ public inline fun geoJsonLayer( * Alias for the [GeoJsonLayer] constructor that provides Kotlin named parameters and default * values. */ -public inline fun geoJsonLayer( +public fun geoJsonLayer( map: GoogleMap, @RawRes resourceId: Int, context: Context, diff --git a/data/src/main/java/com/google/maps/android/data/kml/Kml.kt b/data/src/main/java/com/google/maps/android/data/kml/Kml.kt index 2bd282a0b..6df061988 100644 --- a/data/src/main/java/com/google/maps/android/data/kml/Kml.kt +++ b/data/src/main/java/com/google/maps/android/data/kml/Kml.kt @@ -15,7 +15,6 @@ * */ -@file:Suppress("NOTHING_TO_INLINE") package com.google.maps.android.data.kml import android.content.Context @@ -32,7 +31,7 @@ import java.io.InputStream /** * Alias for the [KmlLayer] constructor that provides Kotlin named parameters and default values. */ -public inline fun kmlLayer( +public fun kmlLayer( map: GoogleMap, @RawRes resourceId: Int, context: Context, @@ -55,7 +54,7 @@ public inline fun kmlLayer( /** * Alias for the [KmlLayer] constructor that provides Kotlin named parameters and default values. */ -public inline fun kmlLayer( +public fun kmlLayer( map: GoogleMap, stream: InputStream, context: Context, diff --git a/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt b/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt index e968ce8d9..d61b06466 100644 --- a/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt +++ b/data/src/main/java/com/google/maps/android/ktx/utils/geojson/GeoJson.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -28,7 +27,7 @@ import org.json.JSONObject import com.google.maps.android.data.geojson.geoJsonLayer as canonicalGeoJsonLayer @Deprecated("Moved to com.google.maps.android.data.geojson.geoJsonLayer", ReplaceWith("geoJsonLayer(map, geoJsonFile, markerManager, polygonManager, polylineManager, groundOverlayManager)", "com.google.maps.android.data.geojson.geoJsonLayer")) -public inline fun geoJsonLayer( +public fun geoJsonLayer( map: GoogleMap, geoJsonFile: JSONObject, markerManager: MarkerManager? = null, @@ -38,7 +37,7 @@ public inline fun geoJsonLayer( ): GeoJsonLayer = canonicalGeoJsonLayer(map, geoJsonFile, markerManager, polygonManager, polylineManager, groundOverlayManager) @Deprecated("Moved to com.google.maps.android.data.geojson.geoJsonLayer", ReplaceWith("geoJsonLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager)", "com.google.maps.android.data.geojson.geoJsonLayer")) -public inline fun geoJsonLayer( +public fun geoJsonLayer( map: GoogleMap, @RawRes resourceId: Int, context: Context, @@ -47,3 +46,4 @@ public inline fun geoJsonLayer( polylineManager: PolylineManager? = null, groundOverlayManager: GroundOverlayManager? = null ): GeoJsonLayer = canonicalGeoJsonLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager) + diff --git a/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt b/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt index 1fc8fce27..921541f4c 100644 --- a/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt +++ b/data/src/main/java/com/google/maps/android/ktx/utils/kml/Kml.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -29,7 +28,7 @@ import java.io.InputStream import com.google.maps.android.data.kml.kmlLayer as canonicalKmlLayer @Deprecated("Moved to com.google.maps.android.data.kml.kmlLayer", ReplaceWith("kmlLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache)", "com.google.maps.android.data.kml.kmlLayer")) -public inline fun kmlLayer( +public fun kmlLayer( map: GoogleMap, @RawRes resourceId: Int, context: Context, @@ -41,7 +40,7 @@ public inline fun kmlLayer( ): KmlLayer = canonicalKmlLayer(map, resourceId, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache) @Deprecated("Moved to com.google.maps.android.data.kml.kmlLayer", ReplaceWith("kmlLayer(map, stream, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache)", "com.google.maps.android.data.kml.kmlLayer")) -public inline fun kmlLayer( +public fun kmlLayer( map: GoogleMap, stream: InputStream, context: Context, @@ -51,3 +50,4 @@ public inline fun kmlLayer( groundOverlayManager: GroundOverlayManager = GroundOverlayManager(map), imagesCache: Renderer.ImagesCache? = null ): KmlLayer = canonicalKmlLayer(map, stream, context, markerManager, polygonManager, polylineManager, groundOverlayManager, imagesCache) + diff --git a/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt index 026cda053..f232203b6 100644 --- a/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt +++ b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt @@ -15,7 +15,6 @@ * */ -@file:Suppress("NOTHING_TO_INLINE") package com.google.maps.android.heatmaps import com.google.android.gms.maps.model.LatLng @@ -26,7 +25,7 @@ import com.google.maps.android.heatmaps.WeightedLatLng /** * Converts this LatLng to a [WeightedLatLng] */ -public inline fun LatLng.toWeightedLatLng( +public fun LatLng.toWeightedLatLng( intensity: Double = WeightedLatLng.DEFAULT_INTENSITY ): WeightedLatLng = WeightedLatLng(this, intensity) @@ -37,7 +36,7 @@ public inline fun LatLng.toWeightedLatLng( * @throws IllegalStateException when [opacity] is not within the range [0, 1] or if [latLngs] is * empty */ -public inline fun heatmapTileProviderWithData( +public fun heatmapTileProviderWithData( latLngs: Collection, radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, @@ -59,7 +58,7 @@ public inline fun heatmapTileProviderWithData( * @throws IllegalStateException when [opacity] is not within the range [0, 1] or if [latLngs] is * empty */ -public inline fun heatmapTileProviderWithWeightedData( +public fun heatmapTileProviderWithWeightedData( latLngs: Collection, radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, diff --git a/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt b/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt index 25c4dedd9..3d62d32c5 100644 --- a/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt +++ b/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -25,12 +24,12 @@ import com.google.maps.android.heatmaps.heatmapTileProviderWithData as canonical import com.google.maps.android.heatmaps.heatmapTileProviderWithWeightedData as canonicalHeatmapTileProviderWithWeightedData @Deprecated("Moved to com.google.maps.android.heatmaps.toWeightedLatLng", ReplaceWith("toWeightedLatLng(intensity)", "com.google.maps.android.heatmaps.toWeightedLatLng")) -public inline fun LatLng.toWeightedLatLng( +public fun LatLng.toWeightedLatLng( intensity: Double = WeightedLatLng.DEFAULT_INTENSITY ): WeightedLatLng = this.canonicalToWeightedLatLng(intensity) @Deprecated("Moved to com.google.maps.android.heatmaps.heatmapTileProviderWithData", ReplaceWith("heatmapTileProviderWithData(latLngs, radius, gradient, opacity, maxIntensity)", "com.google.maps.android.heatmaps.heatmapTileProviderWithData")) -public inline fun heatmapTileProviderWithData( +public fun heatmapTileProviderWithData( latLngs: Collection, radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, @@ -39,10 +38,11 @@ public inline fun heatmapTileProviderWithData( ) : HeatmapTileProvider = canonicalHeatmapTileProviderWithData(latLngs, radius, gradient, opacity, maxIntensity) @Deprecated("Moved to com.google.maps.android.heatmaps.heatmapTileProviderWithWeightedData", ReplaceWith("heatmapTileProviderWithWeightedData(latLngs, radius, gradient, opacity, maxIntensity)", "com.google.maps.android.heatmaps.heatmapTileProviderWithWeightedData")) -public inline fun heatmapTileProviderWithWeightedData( +public fun heatmapTileProviderWithWeightedData( latLngs: Collection, radius: Int = HeatmapTileProvider.DEFAULT_RADIUS, gradient: Gradient = HeatmapTileProvider.DEFAULT_GRADIENT, opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY, maxIntensity: Double = 0.0 ) : HeatmapTileProvider = canonicalHeatmapTileProviderWithWeightedData(latLngs, radius, gradient, opacity, maxIntensity) + diff --git a/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt b/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt index 4ee394232..d993ad869 100644 --- a/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt +++ b/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.heatmaps diff --git a/library/src/main/java/com/google/maps/android/GoogleMap.kt b/library/src/main/java/com/google/maps/android/GoogleMap.kt index e0acc4fba..c7c0c53f6 100644 --- a/library/src/main/java/com/google/maps/android/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/GoogleMap.kt @@ -49,7 +49,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine @IntDef( GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE, @@ -161,8 +160,8 @@ public suspend inline fun GoogleMap.awaitAnimateCamera( * A suspending function that awaits for the map to be loaded. Uses * [GoogleMap.setOnMapLoadedCallback]. */ -public suspend inline fun GoogleMap.awaitMapLoad(): Unit = - suspendCoroutine { continuation -> +public suspend fun GoogleMap.awaitMapLoad(): Unit = + suspendCancellableCoroutine { continuation -> setOnMapLoadedCallback { continuation.resume(Unit) } @@ -218,8 +217,8 @@ public fun GoogleMap.cameraMoveEvents(): Flow = * @param bitmap an optional preallocated bitmap * @return the snapshot */ -public suspend inline fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = - suspendCoroutine { continuation -> +public suspend fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = + suspendCancellableCoroutine { continuation -> snapshot({ continuation.resume(it) }, bitmap) } diff --git a/library/src/main/java/com/google/maps/android/LatLng.kt b/library/src/main/java/com/google/maps/android/LatLng.kt index 13860b67a..d7f7c09aa 100644 --- a/library/src/main/java/com/google/maps/android/LatLng.kt +++ b/library/src/main/java/com/google/maps/android/LatLng.kt @@ -14,8 +14,6 @@ * limitations under the License. * */ -@file:Suppress("NOTHING_TO_INLINE") - package com.google.maps.android import com.google.android.gms.maps.model.LatLng @@ -30,7 +28,7 @@ import com.google.maps.android.SphericalUtil * val (lat, _) = latLng * ``` */ -public inline operator fun LatLng.component1(): Double = this.latitude +public operator fun LatLng.component1(): Double = this.latitude /** * Returns the [LatLng.longitude] of this [LatLng]. @@ -40,7 +38,7 @@ public inline operator fun LatLng.component1(): Double = this.latitude * val (_, lng) = latLng * ``` */ -public inline operator fun LatLng.component2(): Double = this.longitude +public operator fun LatLng.component2(): Double = this.longitude /** * Computes whether the given [latLng] lies on or is near this polyline within [tolerance] (in @@ -53,7 +51,7 @@ public inline operator fun LatLng.component2(): Double = this.longitude * * @see PolyUtil.isLocationOnPath */ -public inline fun List.isLocationOnPath( +public fun List.isLocationOnPath( latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1 @@ -70,7 +68,7 @@ public inline fun List.isLocationOnPath( * * @see PolyUtil.isLocationOnEdge */ -public inline fun List.isOnEdge( +public fun List.isOnEdge( latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1 @@ -91,7 +89,7 @@ public inline fun List.isOnEdge( * * @see PolyUtil.containsLocation */ -public inline fun List.containsLocation(latLng: LatLng, geodesic: Boolean): Boolean = +public fun List.containsLocation(latLng: LatLng, geodesic: Boolean): Boolean = PolyUtil.containsLocation(latLng, this, geodesic) /** @@ -103,7 +101,7 @@ public inline fun List.containsLocation(latLng: LatLng, geodesic: Boolea * * @see PolyUtil.simplify */ -public inline fun List.simplify(tolerance: Double): List = +public fun List.simplify(tolerance: Double): List = PolyUtil.simplify(this, tolerance) /** @@ -113,7 +111,7 @@ public inline fun List.simplify(tolerance: Double): List = * * @see [Polyline Algorithm Format](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) */ -public inline fun String.toLatLngList(): List = PolyUtil.decode(this) +public fun String.toLatLngList(): List = PolyUtil.decode(this) /** * Encodes this [LatLng] list in a String using the @@ -123,7 +121,7 @@ public inline fun String.toLatLngList(): List = PolyUtil.decode(this) * * @see [Polyline Algorithm Format](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) */ -public inline fun List.latLngListEncode(): String = PolyUtil.encode(this) +public fun List.latLngListEncode(): String = PolyUtil.encode(this) /** * Checks whether or not this [LatLng] list is a closed Polygon. @@ -132,21 +130,21 @@ public inline fun List.latLngListEncode(): String = PolyUtil.encode(this * * @see PolyUtil.isClosedPolygon */ -public inline fun List.isClosedPolygon(): Boolean = PolyUtil.isClosedPolygon(this) +public fun List.isClosedPolygon(): Boolean = PolyUtil.isClosedPolygon(this) /** * Computes the length of this path on Earth. * * @return the length of this path in meters */ -public inline fun List.sphericalPathLength(): Double = SphericalUtil.computeLength(this) +public fun List.sphericalPathLength(): Double = SphericalUtil.computeLength(this) /** * Computes the area under a closed path on Earth. * * @return the area in square meters */ -public inline fun List.sphericalPolygonArea(): Double = SphericalUtil.computeArea(this) +public fun List.sphericalPolygonArea(): Double = SphericalUtil.computeArea(this) /** * Computes the signed area under a closed path on Earth. The sign of the area may be used to @@ -154,7 +152,7 @@ public inline fun List.sphericalPolygonArea(): Double = SphericalUtil.co * * @return the signed area in square meters */ -public inline fun List.sphericalPolygonSignedArea(): Double = SphericalUtil.computeSignedArea(this) +public fun List.sphericalPolygonSignedArea(): Double = SphericalUtil.computeSignedArea(this) /** * Computes the heading from this LatLng to [toLatLng]. @@ -164,7 +162,7 @@ public inline fun List.sphericalPolygonSignedArea(): Double = SphericalU * * @see SphericalUtil.computeHeading */ -public inline fun LatLng.sphericalHeading(toLatLng: LatLng): Double = +public fun LatLng.sphericalHeading(toLatLng: LatLng): Double = SphericalUtil.computeHeading(this, toLatLng) /** @@ -176,7 +174,7 @@ public inline fun LatLng.sphericalHeading(toLatLng: LatLng): Double = * * @see SphericalUtil.computeOffset */ -public inline fun LatLng.withSphericalOffset(distance: Double, heading: Double): LatLng = +public fun LatLng.withSphericalOffset(distance: Double, heading: Double): LatLng = SphericalUtil.computeOffset(this, distance, heading) /** @@ -189,7 +187,7 @@ public inline fun LatLng.withSphericalOffset(distance: Double, heading: Double): * * @see SphericalUtil.computeOffsetOrigin */ -public inline fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: Double): LatLng? = +public fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: Double): LatLng? = SphericalUtil.computeOffsetOrigin(this, distance, heading) /** @@ -202,7 +200,7 @@ public inline fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: * * @see [Slerp](http://en.wikipedia.org/wiki/Slerp) */ -public inline fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: Double): LatLng = +public fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: Double): LatLng = SphericalUtil.interpolate(this, to, fraction) /** @@ -211,5 +209,5 @@ public inline fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: * @param to the LatLng to compute the distance to * @return the distance between this and [to] in meters */ -public inline fun LatLng.sphericalDistance(to: LatLng): Double = +public fun LatLng.sphericalDistance(to: LatLng): Double = SphericalUtil.computeDistanceBetween(this, to) diff --git a/library/src/main/java/com/google/maps/android/MapView.kt b/library/src/main/java/com/google/maps/android/MapView.kt index dacf2909b..35162aa48 100644 --- a/library/src/main/java/com/google/maps/android/MapView.kt +++ b/library/src/main/java/com/google/maps/android/MapView.kt @@ -17,8 +17,8 @@ package com.google.maps.android import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.MapView +import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine /** * A suspending function that provides an instance of [GoogleMap] from this [MapView]. This is @@ -26,8 +26,8 @@ import kotlin.coroutines.suspendCoroutine * * @return the [GoogleMap] instance */ -public suspend inline fun MapView.awaitMap(): GoogleMap = - suspendCoroutine { continuation -> +public suspend fun MapView.awaitMap(): GoogleMap = + suspendCancellableCoroutine { continuation -> getMapAsync { continuation.resume(it) } diff --git a/library/src/main/java/com/google/maps/android/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/MapsInitializer.kt index 77bfda674..3a3d46570 100644 --- a/library/src/main/java/com/google/maps/android/MapsInitializer.kt +++ b/library/src/main/java/com/google/maps/android/MapsInitializer.kt @@ -20,9 +20,9 @@ import android.content.Context import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.maps.MapsInitializer +import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine /** * Suspends until the Google Maps SDK is initialized and returns the [MapsInitializer.Renderer] @@ -39,7 +39,7 @@ import kotlin.coroutines.suspendCoroutine * invoke the callback, it resumes with [GooglePlayServicesNotAvailableException] holding the error code. * * **Cancellation:** - * This suspending function does not support cancellation because the underlying Maps SDK + * This suspending function supports standard coroutine cancellation. Note that the underlying Maps SDK * [MapsInitializer.initialize] operation cannot be cancelled once initiated. Only the first * Maps SDK initialization in an application lifecycle honors [preferredRenderer]; passing `null` * uses the SDK's default preference. @@ -49,10 +49,10 @@ import kotlin.coroutines.suspendCoroutine * @throws GooglePlayServicesNotAvailableException if initialization returns a status other than * [ConnectionResult.SUCCESS] without invoking the callback */ -public suspend inline fun Context.awaitMapsSdkInitialized( +public suspend fun Context.awaitMapsSdkInitialized( preferredRenderer: MapsInitializer.Renderer? = null ): MapsInitializer.Renderer = - suspendCoroutine { continuation -> + suspendCancellableCoroutine { continuation -> var callbackInvoked = false val status = MapsInitializer.initialize(this, preferredRenderer) { renderer -> callbackInvoked = true diff --git a/library/src/main/java/com/google/maps/android/Polygon.kt b/library/src/main/java/com/google/maps/android/Polygon.kt index 95487f2b1..7e7cb0934 100644 --- a/library/src/main/java/com/google/maps/android/Polygon.kt +++ b/library/src/main/java/com/google/maps/android/Polygon.kt @@ -15,7 +15,6 @@ * */ -@file:Suppress("NOTHING_TO_INLINE") package com.google.maps.android import com.google.android.gms.maps.model.LatLng @@ -31,7 +30,7 @@ import com.google.maps.android.SphericalUtil * * @see PolyUtil.containsLocation */ -public inline fun Polygon.contains(latLng: LatLng): Boolean = +public fun Polygon.contains(latLng: LatLng): Boolean = PolyUtil.containsLocation(latLng, this.points, this.isGeodesic) /** @@ -44,18 +43,18 @@ public inline fun Polygon.contains(latLng: LatLng): Boolean = * * @see PolyUtil.isLocationOnEdge */ -public inline fun Polygon.isOnEdge(latLng: LatLng, tolerance: Double = 0.1): Boolean = +public fun Polygon.isOnEdge(latLng: LatLng, tolerance: Double = 0.1): Boolean = PolyUtil.isLocationOnEdge(latLng, this.points, this.isGeodesic, tolerance) /** * The area of this Polygon on Earth in square meters. */ -public inline val Polygon.area: Double +public val Polygon.area: Double get() = SphericalUtil.computeArea(this.points) /** * Computes the signed area under a closed path on Earth. The sign of the area may be used to * determine the orientation of the path. */ -public inline val Polygon.signedArea: Double +public val Polygon.signedArea: Double get() = SphericalUtil.computeSignedArea(this.points) diff --git a/library/src/main/java/com/google/maps/android/Polyline.kt b/library/src/main/java/com/google/maps/android/Polyline.kt index de545a0dc..dc8769e8a 100644 --- a/library/src/main/java/com/google/maps/android/Polyline.kt +++ b/library/src/main/java/com/google/maps/android/Polyline.kt @@ -15,7 +15,6 @@ * */ -@file:Suppress("NOTHING_TO_INLINE") package com.google.maps.android import com.google.android.gms.maps.model.LatLng @@ -27,11 +26,11 @@ import com.google.maps.android.SphericalUtil * Computes where the given [latLng] is contained on or near this Polyline within a specified * tolerance in meters. */ -public inline fun Polyline.contains(latLng: LatLng, tolerance: Double = 0.1): Boolean = +public fun Polyline.contains(latLng: LatLng, tolerance: Double = 0.1): Boolean = PolyUtil.isLocationOnPath(latLng, this.points, this.isGeodesic, tolerance) /** * The spherical length of this Polyline on Earth as measured in meters. */ -public inline val Polyline.sphericalPathLength: Double +public val Polyline.sphericalPathLength: Double get() = SphericalUtil.computeLength(this.points) diff --git a/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt index 58ef5754d..47eea8cae 100644 --- a/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt @@ -19,8 +19,8 @@ package com.google.maps.android import com.google.android.gms.maps.StreetViewPanorama import com.google.android.gms.maps.StreetViewPanoramaFragment +import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine /** * A suspending function that provides an instance of a [StreetViewPanorama] from this @@ -30,8 +30,8 @@ import kotlin.coroutines.suspendCoroutine * * @return the [StreetViewPanorama] */ -public suspend inline fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = - suspendCoroutine { continuation -> +public suspend fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCancellableCoroutine { continuation -> getStreetViewPanoramaAsync { continuation.resume(it) } diff --git a/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt index 0f79a0145..7499114b1 100644 --- a/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt @@ -23,8 +23,8 @@ import com.google.android.gms.maps.model.StreetViewPanoramaOrientation import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine /** * A suspending function that provides an instance of a [StreetViewPanorama] from this @@ -34,8 +34,8 @@ import kotlin.coroutines.suspendCoroutine * * @return the [StreetViewPanorama] instance */ -public suspend inline fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = - suspendCoroutine { continuation -> +public suspend fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCancellableCoroutine { continuation -> getStreetViewPanoramaAsync { continuation.resume(it) } diff --git a/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt index 1f29844e7..950939911 100644 --- a/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt +++ b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt @@ -19,8 +19,8 @@ package com.google.maps.android import com.google.android.gms.maps.StreetViewPanorama import com.google.android.gms.maps.SupportStreetViewPanoramaFragment +import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine /** * A suspending function that provides an instance of a [StreetViewPanorama] from this @@ -30,8 +30,8 @@ import kotlin.coroutines.suspendCoroutine * * @return the [StreetViewPanorama] */ -public suspend inline fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = - suspendCoroutine { continuation -> +public suspend fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCancellableCoroutine { continuation -> getStreetViewPanoramaAsync { continuation.resume(it) } diff --git a/library/src/main/java/com/google/maps/android/collections/CircleManager.kt b/library/src/main/java/com/google/maps/android/collections/CircleManager.kt index 7aa82da3e..519edaf1c 100644 --- a/library/src/main/java/com/google/maps/android/collections/CircleManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/CircleManager.kt @@ -18,9 +18,6 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Circle import com.google.android.gms.maps.model.CircleOptions -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -72,31 +69,3 @@ open class CircleManager(map: GoogleMap) : } } } - -/** - * Adds a new [Circle] to the underlying map and to this [CircleManager.Collection] with the - * provided [optionsActions]. - */ -public inline fun CircleManager.Collection.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = - this.addCircle( - CircleOptions().apply(optionsActions) - ) - -/** - * Returns a flow that emits when a circle in this collection is clicked. Using this to observe circle clicks - * will override an existing listener (if any) to [CircleManager.Collection.setOnCircleClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun CircleManager.Collection.clickEvents(): Flow = - callbackFlow { - setOnCircleClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnCircleClickListener(null) - } - } diff --git a/library/src/main/java/com/google/maps/android/collections/CircleManagerFlows.kt b/library/src/main/java/com/google/maps/android/collections/CircleManagerFlows.kt new file mode 100644 index 000000000..f3b92aae0 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/collections/CircleManagerFlows.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.collections + +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Adds a new [Circle] to the underlying map and to this [CircleManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun CircleManager.Collection.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = + this.addCircle( + CircleOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a circle in this collection is clicked. Using this to observe circle clicks + * will override an existing listener (if any) to [CircleManager.Collection.setOnCircleClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + * + * **Note on event consumption**: The underlying SDK listener returns the result of `trySend().isSuccess`. + * When an emission is accepted by the flow buffer, the click event is considered consumed (`true`), + * suppressing default SDK behavior. Under backpressure if the buffer is full, `trySend` returns `false`, + * allowing default SDK click handling to proceed. + */ +public fun CircleManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnCircleClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnCircleClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt b/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt index eaeead850..7dd9d07d1 100644 --- a/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/GroundOverlayManager.kt @@ -18,9 +18,6 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.GroundOverlayOptions -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -75,33 +72,3 @@ open class GroundOverlayManager(map: GoogleMap) : } } } - -/** - * Adds a new [GroundOverlay] to the underlying map and to this [GroundOverlayManager.Collection] - * with the provided [optionsActions]. - */ -public inline fun GroundOverlayManager.Collection.addGroundOverlay( - optionsActions: GroundOverlayOptions.() -> Unit -): GroundOverlay = - this.addGroundOverlay( - GroundOverlayOptions().apply(optionsActions) - ) - -/** - * Returns a flow that emits when a ground overlay in this collection is clicked. Using this to observe ground overlay clicks - * will override an existing listener (if any) to [GroundOverlayManager.Collection.setOnGroundOverlayClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun GroundOverlayManager.Collection.clickEvents(): Flow = - callbackFlow { - setOnGroundOverlayClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnGroundOverlayClickListener(null) - } - } diff --git a/library/src/main/java/com/google/maps/android/collections/GroundOverlayManagerFlows.kt b/library/src/main/java/com/google/maps/android/collections/GroundOverlayManagerFlows.kt new file mode 100644 index 000000000..5786cf099 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/collections/GroundOverlayManagerFlows.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.collections + +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.GroundOverlayOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Adds a new [GroundOverlay] to the underlying map and to this [GroundOverlayManager.Collection] + * with the provided [optionsActions]. + */ +public inline fun GroundOverlayManager.Collection.addGroundOverlay( + optionsActions: GroundOverlayOptions.() -> Unit +): GroundOverlay = + this.addGroundOverlay( + GroundOverlayOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a ground overlay in this collection is clicked. Using this to observe ground overlay clicks + * will override an existing listener (if any) to [GroundOverlayManager.Collection.setOnGroundOverlayClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + * + * **Note on event consumption**: The underlying SDK listener returns the result of `trySend().isSuccess`. + * When an emission is accepted by the flow buffer, the click event is considered consumed (`true`), + * suppressing default SDK behavior. Under backpressure if the buffer is full, `trySend` returns `false`, + * allowing default SDK click handling to proceed. + */ +public fun GroundOverlayManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnGroundOverlayClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnGroundOverlayClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt b/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt index 478af0013..ebb2b873c 100644 --- a/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/MarkerManager.kt @@ -20,9 +20,6 @@ import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.AdvancedMarkerOptions import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.MarkerOptions -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -130,69 +127,3 @@ open class MarkerManager(map: GoogleMap) : } } } - -/** - * Adds a new [Marker] to the underlying map and to this [MarkerManager.Collection] with the - * provided [optionsActions]. - */ -public inline fun MarkerManager.Collection.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker = - this.addMarker( - MarkerOptions().apply(optionsActions) - ) - -/** - * Returns a flow that emits when a marker in this collection is clicked. Using this to observe marker clicks - * will override an existing listener (if any) to [MarkerManager.Collection.setOnMarkerClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun MarkerManager.Collection.clickEvents(): Flow = - callbackFlow { - setOnMarkerClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnMarkerClickListener(null) - } - } - -/** - * Returns a flow that emits when a marker's info window in this collection is clicked. Using this to observe info window clicks - * will override an existing listener (if any) to [MarkerManager.Collection.setOnInfoWindowClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun MarkerManager.Collection.infoWindowClickEvents(): Flow = - callbackFlow { - setOnInfoWindowClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnInfoWindowClickListener(null) - } - } - -/** - * Returns a flow that emits when a marker's info window in this collection is long clicked. Using this to observe info window long clicks - * will override an existing listener (if any) to [MarkerManager.Collection.setOnInfoWindowLongClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun MarkerManager.Collection.infoWindowLongClickEvents(): Flow = - callbackFlow { - setOnInfoWindowLongClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnInfoWindowLongClickListener(null) - } - } diff --git a/library/src/main/java/com/google/maps/android/collections/MarkerManagerFlows.kt b/library/src/main/java/com/google/maps/android/collections/MarkerManagerFlows.kt new file mode 100644 index 000000000..f4eff73b9 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/collections/MarkerManagerFlows.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.collections + +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Adds a new [Marker] to the underlying map and to this [MarkerManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun MarkerManager.Collection.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker = + this.addMarker( + MarkerOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a marker in this collection is clicked. Using this to observe marker clicks + * will override an existing listener (if any) to [MarkerManager.Collection.setOnMarkerClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + * + * **Note on event consumption**: The underlying SDK listener returns the result of `trySend().isSuccess`. + * When an emission is accepted by the flow buffer, the click event is considered consumed (`true`), + * suppressing default SDK behavior. Under backpressure if the buffer is full, `trySend` returns `false`, + * allowing default SDK click handling to proceed. + */ +public fun MarkerManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnMarkerClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnMarkerClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window in this collection is clicked. Using this to observe info window clicks + * will override an existing listener (if any) to [MarkerManager.Collection.setOnInfoWindowClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun MarkerManager.Collection.infoWindowClickEvents(): Flow = + callbackFlow { + setOnInfoWindowClickListener { + trySend(it) + } + awaitClose { + setOnInfoWindowClickListener(null) + } + } + +/** + * Returns a flow that emits when a marker's info window in this collection is long clicked. Using this to observe info window + * long clicks will override an existing listener (if any) to [MarkerManager.Collection.setOnInfoWindowLongClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + */ +public fun MarkerManager.Collection.infoWindowLongClickEvents(): Flow = + callbackFlow { + setOnInfoWindowLongClickListener { + trySend(it) + } + awaitClose { + setOnInfoWindowLongClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt b/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt index 83dc489d4..98eef696a 100644 --- a/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/PolygonManager.kt @@ -18,9 +18,6 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Polygon import com.google.android.gms.maps.model.PolygonOptions -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -72,33 +69,3 @@ open class PolygonManager(map: GoogleMap) : } } } - -/** - * Adds a new [Polygon] to the underlying map and to this [PolygonManager.Collection] with the - * provided [optionsActions]. - */ -public inline fun PolygonManager.Collection.addPolygon( - optionsActions: PolygonOptions.() -> Unit -): Polygon = - this.addPolygon( - PolygonOptions().apply(optionsActions) - ) - -/** - * Returns a flow that emits when a polygon in this collection is clicked. Using this to observe polygon clicks - * will override an existing listener (if any) to [PolygonManager.Collection.setOnPolygonClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun PolygonManager.Collection.clickEvents(): Flow = - callbackFlow { - setOnPolygonClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnPolygonClickListener(null) - } - } diff --git a/library/src/main/java/com/google/maps/android/collections/PolygonManagerFlows.kt b/library/src/main/java/com/google/maps/android/collections/PolygonManagerFlows.kt new file mode 100644 index 000000000..cc7f71494 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/collections/PolygonManagerFlows.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.collections + +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Adds a new [Polygon] to the underlying map and to this [PolygonManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun PolygonManager.Collection.addPolygon( + optionsActions: PolygonOptions.() -> Unit +): Polygon = + this.addPolygon( + PolygonOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a polygon in this collection is clicked. Using this to observe polygon clicks + * will override an existing listener (if any) to [PolygonManager.Collection.setOnPolygonClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + * + * **Note on event consumption**: The underlying SDK listener returns the result of `trySend().isSuccess`. + * When an emission is accepted by the flow buffer, the click event is considered consumed (`true`), + * suppressing default SDK behavior. Under backpressure if the buffer is full, `trySend` returns `false`, + * allowing default SDK click handling to proceed. + */ +public fun PolygonManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnPolygonClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnPolygonClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt b/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt index 55f305b55..61de4b7ef 100644 --- a/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/PolylineManager.kt @@ -18,9 +18,6 @@ package com.google.maps.android.collections import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Polyline import com.google.android.gms.maps.model.PolylineOptions -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlin.collections.Collection as KotlinCollection /** @@ -74,33 +71,3 @@ open class PolylineManager(map: GoogleMap) : } } } - -/** - * Adds a new [Polyline] to the underlying map and to this [PolylineManager.Collection] with the - * provided [optionsActions]. - */ -public inline fun PolylineManager.Collection.addPolyline( - optionsActions: PolylineOptions.() -> Unit -): Polyline = - this.addPolyline( - PolylineOptions().apply(optionsActions) - ) - -/** - * Returns a flow that emits when a polyline in this collection is clicked. Using this to observe polyline clicks - * will override an existing listener (if any) to [PolylineManager.Collection.setOnPolylineClickListener]. - * - * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing - * multiple collectors will result in listener hijacking, and cancelling any observer will unregister - * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) - * for multi-observer configurations. - */ -public fun PolylineManager.Collection.clickEvents(): Flow = - callbackFlow { - setOnPolylineClickListener { - trySend(it).isSuccess - } - awaitClose { - setOnPolylineClickListener(null) - } - } diff --git a/library/src/main/java/com/google/maps/android/collections/PolylineManagerFlows.kt b/library/src/main/java/com/google/maps/android/collections/PolylineManagerFlows.kt new file mode 100644 index 000000000..ce6f35259 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/collections/PolylineManagerFlows.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.collections + +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +/** + * Adds a new [Polyline] to the underlying map and to this [PolylineManager.Collection] with the + * provided [optionsActions]. + */ +public inline fun PolylineManager.Collection.addPolyline( + optionsActions: PolylineOptions.() -> Unit +): Polyline = + this.addPolyline( + PolylineOptions().apply(optionsActions) + ) + +/** + * Returns a flow that emits when a polyline in this collection is clicked. Using this to observe polyline clicks + * will override an existing listener (if any) to [PolylineManager.Collection.setOnPolylineClickListener]. + * + * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing + * multiple collectors will result in listener hijacking, and cancelling any observer will unregister + * the active listener completely. Always share this flow (e.g. using [kotlinx.coroutines.flow.shareIn]) + * for multi-observer configurations. + * + * **Note on event consumption**: The underlying SDK listener returns the result of `trySend().isSuccess`. + * When an emission is accepted by the flow buffer, the click event is considered consumed (`true`), + * suppressing default SDK behavior. Under backpressure if the buffer is full, `trySend` returns `false`, + * allowing default SDK click handling to proceed. + */ +public fun PolylineManager.Collection.clickEvents(): Flow = + callbackFlow { + setOnPolylineClickListener { + trySend(it).isSuccess + } + awaitClose { + setOnPolylineClickListener(null) + } + } diff --git a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt index 2162754b9..92243b422 100644 --- a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx + import android.graphics.Bitmap import android.location.Location import com.google.android.gms.maps.CameraUpdate @@ -116,7 +118,7 @@ public fun GoogleMap.cameraEvents(): Flow = this.canonicalCameraEve public suspend inline fun GoogleMap.awaitAnimateCamera(cameraUpdate: CameraUpdate, durationMs: Int = 3000): Unit = this.canonicalAwaitAnimateCamera(cameraUpdate, durationMs) @Deprecated("Moved to com.google.maps.android.awaitMapLoad", ReplaceWith("awaitMapLoad()", "com.google.maps.android.awaitMapLoad")) -public suspend inline fun GoogleMap.awaitMapLoad(): Unit = this.canonicalAwaitMapLoad() +public suspend fun GoogleMap.awaitMapLoad(): Unit = this.canonicalAwaitMapLoad() @Deprecated("Moved to com.google.maps.android.cameraIdleEvents", ReplaceWith("cameraIdleEvents()", "com.google.maps.android.cameraIdleEvents")) public fun GoogleMap.cameraIdleEvents(): Flow = this.canonicalCameraIdleEvents() @@ -128,7 +130,8 @@ public fun GoogleMap.cameraMoveCanceledEvents(): Flow = this.canonicalCame public fun GoogleMap.cameraMoveEvents(): Flow = this.canonicalCameraMoveEvents() @Deprecated("Moved to com.google.maps.android.awaitSnapshot", ReplaceWith("awaitSnapshot(bitmap)", "com.google.maps.android.awaitSnapshot")) -public suspend inline fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = this.canonicalAwaitSnapshot(bitmap) +public suspend fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = this.canonicalAwaitSnapshot(bitmap) + @Deprecated("Moved to com.google.maps.android.cameraMoveStartedEvents", ReplaceWith("cameraMoveStartedEvents()", "com.google.maps.android.cameraMoveStartedEvents")) public fun GoogleMap.cameraMoveStartedEvents(): Flow = this.canonicalCameraMoveStartedEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt b/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt index 4dea2d039..41e8a200a 100644 --- a/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt +++ b/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -25,4 +24,5 @@ import com.google.maps.android.awaitMap as canonicalAwaitMap replaceWith = ReplaceWith("awaitMap()", "com.google.maps.android.awaitMap"), level = DeprecationLevel.WARNING ) -public suspend inline fun MapFragment.awaitMap(): GoogleMap = this.canonicalAwaitMap() +public suspend fun MapFragment.awaitMap(): GoogleMap = this.canonicalAwaitMap() + diff --git a/library/src/main/java/com/google/maps/android/ktx/MapView.kt b/library/src/main/java/com/google/maps/android/ktx/MapView.kt index 3c407dad5..128df239d 100644 --- a/library/src/main/java/com/google/maps/android/ktx/MapView.kt +++ b/library/src/main/java/com/google/maps/android/ktx/MapView.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -25,4 +24,5 @@ import com.google.maps.android.awaitMap as canonicalAwaitMap replaceWith = ReplaceWith("awaitMap()", "com.google.maps.android.awaitMap"), level = DeprecationLevel.WARNING ) -public suspend inline fun MapView.awaitMap(): GoogleMap = this.canonicalAwaitMap() +public suspend fun MapView.awaitMap(): GoogleMap = this.canonicalAwaitMap() + diff --git a/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt index 24e93a530..19e18030d 100644 --- a/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt +++ b/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -34,6 +33,7 @@ import com.google.maps.android.awaitMapsSdkInitialized as canonicalAwaitMapsSdkI ), level = DeprecationLevel.WARNING ) -public suspend inline fun Context.awaitMapsSdkInitialized( +public suspend fun Context.awaitMapsSdkInitialized( preferredRenderer: MapsInitializer.Renderer? = null ): MapsInitializer.Renderer = this.canonicalAwaitMapsSdkInitialized(preferredRenderer) + diff --git a/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt index 340f49f68..3dccb8729 100644 --- a/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt +++ b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -25,4 +24,5 @@ import com.google.maps.android.awaitStreetViewPanorama as canonicalAwaitStreetVi replaceWith = ReplaceWith("awaitStreetViewPanorama()", "com.google.maps.android.awaitStreetViewPanorama"), level = DeprecationLevel.WARNING ) -public suspend inline fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() +public suspend fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() + diff --git a/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt index 77ad58644..dfd5b9c6d 100644 --- a/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt +++ b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -33,7 +32,8 @@ import com.google.maps.android.longClickEvents as canonicalLongClickEvents replaceWith = ReplaceWith("awaitStreetViewPanorama()", "com.google.maps.android.awaitStreetViewPanorama"), level = DeprecationLevel.WARNING ) -public suspend inline fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() +public suspend fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() + @Deprecated( message = "Use com.google.maps.android.cameraChangeEvents instead", diff --git a/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt b/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt index 4b4313704..8621a0683 100644 --- a/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt +++ b/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -25,4 +24,5 @@ import com.google.maps.android.awaitMap as canonicalAwaitMap replaceWith = ReplaceWith("awaitMap()", "com.google.maps.android.awaitMap"), level = DeprecationLevel.WARNING ) -public suspend inline fun SupportMapFragment.awaitMap(): GoogleMap = this.canonicalAwaitMap() +public suspend fun SupportMapFragment.awaitMap(): GoogleMap = this.canonicalAwaitMap() + diff --git a/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt index 0a4513fbb..07cc270c3 100644 --- a/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt +++ b/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -25,4 +24,5 @@ import com.google.maps.android.awaitStreetViewPanorama as canonicalAwaitStreetVi replaceWith = ReplaceWith("awaitStreetViewPanorama()", "com.google.maps.android.awaitStreetViewPanorama"), level = DeprecationLevel.WARNING ) -public suspend inline fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() +public suspend fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = this.canonicalAwaitStreetViewPanorama() + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt b/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt index b71eb836d..2b3ee122e 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/CameraPosition.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.CameraPosition -import com.google.maps.android.model.cameraPosition as canonical_cameraPosition +import com.google.maps.android.model.cameraPosition as canonicalCameraPosition @Deprecated( message = "Use com.google.maps.android.model.cameraPosition instead", replaceWith = ReplaceWith("cameraPosition(optionsActions)", "com.google.maps.android.model.cameraPosition"), level = DeprecationLevel.WARNING ) -public inline fun cameraPosition(optionsActions: CameraPosition.Builder.() -> Unit): CameraPosition = canonical_cameraPosition(optionsActions) +public inline fun cameraPosition(optionsActions: CameraPosition.Builder.() -> Unit): CameraPosition = canonicalCameraPosition(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt index 11c06fb46..9a0eb5f51 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.CircleOptions -import com.google.maps.android.model.circleOptions as canonical_circleOptions +import com.google.maps.android.model.circleOptions as canonicalCircleOptions @Deprecated( message = "Use com.google.maps.android.model.circleOptions instead", replaceWith = ReplaceWith("circleOptions(optionsActions)", "com.google.maps.android.model.circleOptions"), level = DeprecationLevel.WARNING ) -public inline fun circleOptions(optionsActions: CircleOptions.() -> Unit): CircleOptions = canonical_circleOptions(optionsActions) +public inline fun circleOptions(optionsActions: CircleOptions.() -> Unit): CircleOptions = canonicalCircleOptions(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt index bae64df96..9e285218d 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.GroundOverlayOptions -import com.google.maps.android.model.groundOverlayOptions as canonical_groundOverlayOptions +import com.google.maps.android.model.groundOverlayOptions as canonicalGroundOverlayOptions @Deprecated( message = "Use com.google.maps.android.model.groundOverlayOptions instead", replaceWith = ReplaceWith("groundOverlayOptions(optionsActions)", "com.google.maps.android.model.groundOverlayOptions"), level = DeprecationLevel.WARNING ) -public inline fun groundOverlayOptions(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlayOptions = canonical_groundOverlayOptions(optionsActions) +public inline fun groundOverlayOptions(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlayOptions = canonicalGroundOverlayOptions(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt index c1c087362..81ea9d377 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.MarkerOptions -import com.google.maps.android.model.markerOptions as canonical_markerOptions +import com.google.maps.android.model.markerOptions as canonicalMarkerOptions @Deprecated( message = "Use com.google.maps.android.model.markerOptions instead", replaceWith = ReplaceWith("markerOptions(optionsActions)", "com.google.maps.android.model.markerOptions"), level = DeprecationLevel.WARNING ) -public inline fun markerOptions(optionsActions: MarkerOptions.() -> Unit): MarkerOptions = canonical_markerOptions(optionsActions) +public inline fun markerOptions(optionsActions: MarkerOptions.() -> Unit): MarkerOptions = canonicalMarkerOptions(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt index f86f3925e..de0bdca90 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.PolygonOptions -import com.google.maps.android.model.polygonOptions as canonical_polygonOptions +import com.google.maps.android.model.polygonOptions as canonicalPolygonOptions @Deprecated( message = "Use com.google.maps.android.model.polygonOptions instead", replaceWith = ReplaceWith("polygonOptions(optionsActions)", "com.google.maps.android.model.polygonOptions"), level = DeprecationLevel.WARNING ) -public inline fun polygonOptions(optionsActions: PolygonOptions.() -> Unit): PolygonOptions = canonical_polygonOptions(optionsActions) +public inline fun polygonOptions(optionsActions: PolygonOptions.() -> Unit): PolygonOptions = canonicalPolygonOptions(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt index e3387f4b4..b86868529 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.PolylineOptions -import com.google.maps.android.model.polylineOptions as canonical_polylineOptions +import com.google.maps.android.model.polylineOptions as canonicalPolylineOptions @Deprecated( message = "Use com.google.maps.android.model.polylineOptions instead", replaceWith = ReplaceWith("polylineOptions(optionsActions)", "com.google.maps.android.model.polylineOptions"), level = DeprecationLevel.WARNING ) -public inline fun polylineOptions(optionsActions: PolylineOptions.() -> Unit): PolylineOptions = canonical_polylineOptions(optionsActions) +public inline fun polylineOptions(optionsActions: PolylineOptions.() -> Unit): PolylineOptions = canonicalPolylineOptions(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt index 3a32887e3..6112674a9 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.StreetViewPanoramaCamera -import com.google.maps.android.model.streetViewPanoramaCamera as canonical_streetViewPanoramaCamera +import com.google.maps.android.model.streetViewPanoramaCamera as canonicalStreetViewPanoramaCamera @Deprecated( message = "Use com.google.maps.android.model.streetViewPanoramaCamera instead", replaceWith = ReplaceWith("streetViewPanoramaCamera(optionsActions)", "com.google.maps.android.model.streetViewPanoramaCamera"), level = DeprecationLevel.WARNING ) -public inline fun streetViewPanoramaCamera(optionsActions: StreetViewPanoramaCamera.Builder.() -> Unit): StreetViewPanoramaCamera = canonical_streetViewPanoramaCamera(optionsActions) +public inline fun streetViewPanoramaCamera(optionsActions: StreetViewPanoramaCamera.Builder.() -> Unit): StreetViewPanoramaCamera = canonicalStreetViewPanoramaCamera(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt index 9c326acad..ea9582e48 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.StreetViewPanoramaOrientation -import com.google.maps.android.model.streetViewPanoramaOrientation as canonical_streetViewPanoramaOrientation +import com.google.maps.android.model.streetViewPanoramaOrientation as canonicalStreetViewPanoramaOrientation @Deprecated( message = "Use com.google.maps.android.model.streetViewPanoramaOrientation instead", replaceWith = ReplaceWith("streetViewPanoramaOrientation(optionsActions)", "com.google.maps.android.model.streetViewPanoramaOrientation"), level = DeprecationLevel.WARNING ) -public inline fun streetViewPanoramaOrientation(optionsActions: StreetViewPanoramaOrientation.Builder.() -> Unit): StreetViewPanoramaOrientation = canonical_streetViewPanoramaOrientation(optionsActions) +public inline fun streetViewPanoramaOrientation(optionsActions: StreetViewPanoramaOrientation.Builder.() -> Unit): StreetViewPanoramaOrientation = canonicalStreetViewPanoramaOrientation(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt b/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt index cc008bb6f..9c2e27dcc 100644 --- a/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt +++ b/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.TileOverlayOptions -import com.google.maps.android.model.tileOverlayOptions as canonical_tileOverlayOptions +import com.google.maps.android.model.tileOverlayOptions as canonicalTileOverlayOptions @Deprecated( message = "Use com.google.maps.android.model.tileOverlayOptions instead", replaceWith = ReplaceWith("tileOverlayOptions(optionsActions)", "com.google.maps.android.model.tileOverlayOptions"), level = DeprecationLevel.WARNING ) -public inline fun tileOverlayOptions(optionsActions: TileOverlayOptions.() -> Unit): TileOverlayOptions = canonical_tileOverlayOptions(optionsActions) +public inline fun tileOverlayOptions(optionsActions: TileOverlayOptions.() -> Unit): TileOverlayOptions = canonicalTileOverlayOptions(optionsActions) + diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt b/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt index a401b441e..cb7e7174f 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -36,52 +35,53 @@ import com.google.maps.android.withSphericalLinearInterpolation as canonicalWith import com.google.maps.android.sphericalDistance as canonicalSphericalDistance @Deprecated("Moved to com.google.maps.android.component1", ReplaceWith("component1()", "com.google.maps.android.component1")) -public inline operator fun LatLng.component1(): Double = this.canonicalComponent1() +public operator fun LatLng.component1(): Double = this.canonicalComponent1() @Deprecated("Moved to com.google.maps.android.component2", ReplaceWith("component2()", "com.google.maps.android.component2")) -public inline operator fun LatLng.component2(): Double = this.canonicalComponent2() +public operator fun LatLng.component2(): Double = this.canonicalComponent2() @Deprecated("Moved to com.google.maps.android.isLocationOnPath", ReplaceWith("isLocationOnPath(latLng, geodesic, tolerance)", "com.google.maps.android.isLocationOnPath")) -public inline fun List.isLocationOnPath(latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1): Boolean = this.canonicalIsLocationOnPath(latLng, geodesic, tolerance) +public fun List.isLocationOnPath(latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1): Boolean = this.canonicalIsLocationOnPath(latLng, geodesic, tolerance) @Deprecated("Moved to com.google.maps.android.isOnEdge", ReplaceWith("isOnEdge(latLng, geodesic, tolerance)", "com.google.maps.android.isOnEdge")) -public inline fun List.isOnEdge(latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1): Boolean = this.canonicalIsOnEdge(latLng, geodesic, tolerance) +public fun List.isOnEdge(latLng: LatLng, geodesic: Boolean, tolerance: Double = 0.1): Boolean = this.canonicalIsOnEdge(latLng, geodesic, tolerance) @Deprecated("Moved to com.google.maps.android.containsLocation", ReplaceWith("containsLocation(latLng, geodesic)", "com.google.maps.android.containsLocation")) -public inline fun List.containsLocation(latLng: LatLng, geodesic: Boolean): Boolean = this.canonicalContainsLocation(latLng, geodesic) +public fun List.containsLocation(latLng: LatLng, geodesic: Boolean): Boolean = this.canonicalContainsLocation(latLng, geodesic) @Deprecated("Moved to com.google.maps.android.simplify", ReplaceWith("simplify(tolerance)", "com.google.maps.android.simplify")) -public inline fun List.simplify(tolerance: Double): List = this.canonicalSimplify(tolerance) +public fun List.simplify(tolerance: Double): List = this.canonicalSimplify(tolerance) @Deprecated("Moved to com.google.maps.android.toLatLngList", ReplaceWith("toLatLngList()", "com.google.maps.android.toLatLngList")) -public inline fun String.toLatLngList(): List = this.canonicalToLatLngList() +public fun String.toLatLngList(): List = this.canonicalToLatLngList() @Deprecated("Moved to com.google.maps.android.latLngListEncode", ReplaceWith("latLngListEncode()", "com.google.maps.android.latLngListEncode")) -public inline fun List.latLngListEncode(): String = this.canonicalLatLngListEncode() +public fun List.latLngListEncode(): String = this.canonicalLatLngListEncode() @Deprecated("Moved to com.google.maps.android.isClosedPolygon", ReplaceWith("isClosedPolygon()", "com.google.maps.android.isClosedPolygon")) -public inline fun List.isClosedPolygon(): Boolean = this.canonicalIsClosedPolygon() +public fun List.isClosedPolygon(): Boolean = this.canonicalIsClosedPolygon() @Deprecated("Moved to com.google.maps.android.sphericalPathLength", ReplaceWith("sphericalPathLength()", "com.google.maps.android.sphericalPathLength")) -public inline fun List.sphericalPathLength(): Double = this.canonicalSphericalPathLength() +public fun List.sphericalPathLength(): Double = this.canonicalSphericalPathLength() @Deprecated("Moved to com.google.maps.android.sphericalPolygonArea", ReplaceWith("sphericalPolygonArea()", "com.google.maps.android.sphericalPolygonArea")) -public inline fun List.sphericalPolygonArea(): Double = this.canonicalSphericalPolygonArea() +public fun List.sphericalPolygonArea(): Double = this.canonicalSphericalPolygonArea() @Deprecated("Moved to com.google.maps.android.sphericalPolygonSignedArea", ReplaceWith("sphericalPolygonSignedArea()", "com.google.maps.android.sphericalPolygonSignedArea")) -public inline fun List.sphericalPolygonSignedArea(): Double = this.canonicalSphericalPolygonSignedArea() +public fun List.sphericalPolygonSignedArea(): Double = this.canonicalSphericalPolygonSignedArea() @Deprecated("Moved to com.google.maps.android.sphericalHeading", ReplaceWith("sphericalHeading(toLatLng)", "com.google.maps.android.sphericalHeading")) -public inline fun LatLng.sphericalHeading(toLatLng: LatLng): Double = this.canonicalSphericalHeading(toLatLng) +public fun LatLng.sphericalHeading(toLatLng: LatLng): Double = this.canonicalSphericalHeading(toLatLng) @Deprecated("Moved to com.google.maps.android.withSphericalOffset", ReplaceWith("withSphericalOffset(distance, heading)", "com.google.maps.android.withSphericalOffset")) -public inline fun LatLng.withSphericalOffset(distance: Double, heading: Double): LatLng = this.canonicalWithSphericalOffset(distance, heading) +public fun LatLng.withSphericalOffset(distance: Double, heading: Double): LatLng = this.canonicalWithSphericalOffset(distance, heading) @Deprecated("Moved to com.google.maps.android.computeSphericalOffsetOrigin", ReplaceWith("computeSphericalOffsetOrigin(distance, heading)", "com.google.maps.android.computeSphericalOffsetOrigin")) -public inline fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: Double): LatLng? = this.canonicalComputeSphericalOffsetOrigin(distance, heading) +public fun LatLng.computeSphericalOffsetOrigin(distance: Double, heading: Double): LatLng? = this.canonicalComputeSphericalOffsetOrigin(distance, heading) @Deprecated("Moved to com.google.maps.android.withSphericalLinearInterpolation", ReplaceWith("withSphericalLinearInterpolation(to, fraction)", "com.google.maps.android.withSphericalLinearInterpolation")) -public inline fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: Double): LatLng = this.canonicalWithSphericalLinearInterpolation(to, fraction) +public fun LatLng.withSphericalLinearInterpolation(to: LatLng, fraction: Double): LatLng = this.canonicalWithSphericalLinearInterpolation(to, fraction) @Deprecated("Moved to com.google.maps.android.sphericalDistance", ReplaceWith("sphericalDistance(to)", "com.google.maps.android.sphericalDistance")) -public inline fun LatLng.sphericalDistance(to: LatLng): Double = this.canonicalSphericalDistance(to) +public fun LatLng.sphericalDistance(to: LatLng): Double = this.canonicalSphericalDistance(to) + diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt b/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt index 5d0f2062c..48d71680d 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -24,13 +23,14 @@ import com.google.maps.android.area as canonicalArea import com.google.maps.android.signedArea as canonicalSignedArea @Deprecated("Moved to com.google.maps.android.contains", ReplaceWith("contains(latLng)", "com.google.maps.android.contains")) -public inline fun Polygon.contains(latLng: LatLng): Boolean = this.canonicalContains(latLng) +public fun Polygon.contains(latLng: LatLng): Boolean = this.canonicalContains(latLng) @Deprecated("Moved to com.google.maps.android.isOnEdge", ReplaceWith("isOnEdge(latLng, tolerance)", "com.google.maps.android.isOnEdge")) -public inline fun Polygon.isOnEdge(latLng: LatLng, tolerance: Double = 0.1): Boolean = this.canonicalIsOnEdge(latLng, tolerance) +public fun Polygon.isOnEdge(latLng: LatLng, tolerance: Double = 0.1): Boolean = this.canonicalIsOnEdge(latLng, tolerance) @Deprecated("Moved to com.google.maps.android.area", ReplaceWith("area", "com.google.maps.android.area")) -public inline val Polygon.area: Double get() = this.canonicalArea +public val Polygon.area: Double get() = this.canonicalArea @Deprecated("Moved to com.google.maps.android.signedArea", ReplaceWith("signedArea", "com.google.maps.android.signedArea")) -public inline val Polygon.signedArea: Double get() = this.canonicalSignedArea +public val Polygon.signedArea: Double get() = this.canonicalSignedArea + diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt b/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt index bae32a894..0f0850670 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -22,7 +21,8 @@ import com.google.maps.android.contains as canonicalContains import com.google.maps.android.sphericalPathLength as canonicalSphericalPathLength @Deprecated("Moved to com.google.maps.android.contains", ReplaceWith("contains(latLng, tolerance)", "com.google.maps.android.contains")) -public inline fun Polyline.contains(latLng: LatLng, tolerance: Double = 0.1): Boolean = this.canonicalContains(latLng, tolerance) +public fun Polyline.contains(latLng: LatLng, tolerance: Double = 0.1): Boolean = this.canonicalContains(latLng, tolerance) @Deprecated("Moved to com.google.maps.android.sphericalPathLength", ReplaceWith("sphericalPathLength", "com.google.maps.android.sphericalPathLength")) -public inline val Polyline.sphericalPathLength: Double get() = this.canonicalSphericalPathLength +public val Polyline.sphericalPathLength: Double get() = this.canonicalSphericalPathLength + diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt index de478bdef..d45562e7a 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.utils.collection import com.google.android.gms.maps.model.Circle import com.google.android.gms.maps.model.CircleOptions import kotlinx.coroutines.flow.Flow import com.google.maps.android.collections.CircleManager -import com.google.maps.android.collections.addCircle as canonical_addCircle +import com.google.maps.android.collections.addCircle as canonicalAddCircle import com.google.maps.android.collections.clickEvents as canonicalClickEvents import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents @@ -30,7 +31,7 @@ import com.google.maps.android.collections.infoWindowLongClickEvents as canonica replaceWith = ReplaceWith("addCircle(optionsActions)", "com.google.maps.android.collections.addCircle"), level = DeprecationLevel.WARNING ) -public inline fun CircleManager.Collection.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = this.canonical_addCircle(optionsActions) +public inline fun CircleManager.Collection.addCircle(optionsActions: CircleOptions.() -> Unit): Circle = this.canonicalAddCircle(optionsActions) @Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) public fun CircleManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt index ec15411f2..61a7267da 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.utils.collection import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.GroundOverlayOptions import kotlinx.coroutines.flow.Flow import com.google.maps.android.collections.GroundOverlayManager -import com.google.maps.android.collections.addGroundOverlay as canonical_addGroundOverlay +import com.google.maps.android.collections.addGroundOverlay as canonicalAddGroundOverlay import com.google.maps.android.collections.clickEvents as canonicalClickEvents import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents @@ -30,7 +31,8 @@ import com.google.maps.android.collections.infoWindowLongClickEvents as canonica replaceWith = ReplaceWith("addGroundOverlay(optionsActions)", "com.google.maps.android.collections.addGroundOverlay"), level = DeprecationLevel.WARNING ) -public inline fun GroundOverlayManager.Collection.addGroundOverlay(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlay = this.canonical_addGroundOverlay(optionsActions) +public inline fun GroundOverlayManager.Collection.addGroundOverlay(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlay = this.canonicalAddGroundOverlay(optionsActions) @Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) public fun GroundOverlayManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() + diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt index 81283607b..f24a8a989 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.utils.collection import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.MarkerOptions import kotlinx.coroutines.flow.Flow import com.google.maps.android.collections.MarkerManager -import com.google.maps.android.collections.addMarker as canonical_addMarker +import com.google.maps.android.collections.addMarker as canonicalAddMarker import com.google.maps.android.collections.clickEvents as canonicalClickEvents import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents @@ -30,7 +31,7 @@ import com.google.maps.android.collections.infoWindowLongClickEvents as canonica replaceWith = ReplaceWith("addMarker(optionsActions)", "com.google.maps.android.collections.addMarker"), level = DeprecationLevel.WARNING ) -public inline fun MarkerManager.Collection.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker = this.canonical_addMarker(optionsActions) +public inline fun MarkerManager.Collection.addMarker(optionsActions: MarkerOptions.() -> Unit): Marker = this.canonicalAddMarker(optionsActions) @Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) public fun MarkerManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt index 00cd36ace..e81af8b6a 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.utils.collection import com.google.android.gms.maps.model.Polygon import com.google.android.gms.maps.model.PolygonOptions import kotlinx.coroutines.flow.Flow import com.google.maps.android.collections.PolygonManager -import com.google.maps.android.collections.addPolygon as canonical_addPolygon +import com.google.maps.android.collections.addPolygon as canonicalAddPolygon import com.google.maps.android.collections.clickEvents as canonicalClickEvents import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents @@ -30,7 +31,7 @@ import com.google.maps.android.collections.infoWindowLongClickEvents as canonica replaceWith = ReplaceWith("addPolygon(optionsActions)", "com.google.maps.android.collections.addPolygon"), level = DeprecationLevel.WARNING ) -public inline fun PolygonManager.Collection.addPolygon(optionsActions: PolygonOptions.() -> Unit): Polygon = this.canonical_addPolygon(optionsActions) +public inline fun PolygonManager.Collection.addPolygon(optionsActions: PolygonOptions.() -> Unit): Polygon = this.canonicalAddPolygon(optionsActions) @Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) public fun PolygonManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt index 78011d35a..944da71a0 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.kt @@ -1,4 +1,3 @@ -@file:Suppress("NOTHING_TO_INLINE") /* * Copyright 2026 Google LLC * @@ -14,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.google.maps.android.ktx.utils.collection import com.google.android.gms.maps.model.Polyline import com.google.android.gms.maps.model.PolylineOptions import kotlinx.coroutines.flow.Flow import com.google.maps.android.collections.PolylineManager -import com.google.maps.android.collections.addPolyline as canonical_addPolyline +import com.google.maps.android.collections.addPolyline as canonicalAddPolyline import com.google.maps.android.collections.clickEvents as canonicalClickEvents import com.google.maps.android.collections.infoWindowClickEvents as canonicalInfoWindowClickEvents import com.google.maps.android.collections.infoWindowLongClickEvents as canonicalInfoWindowLongClickEvents @@ -30,7 +31,7 @@ import com.google.maps.android.collections.infoWindowLongClickEvents as canonica replaceWith = ReplaceWith("addPolyline(optionsActions)", "com.google.maps.android.collections.addPolyline"), level = DeprecationLevel.WARNING ) -public inline fun PolylineManager.Collection.addPolyline(optionsActions: PolylineOptions.() -> Unit): Polyline = this.canonical_addPolyline(optionsActions) +public inline fun PolylineManager.Collection.addPolyline(optionsActions: PolylineOptions.() -> Unit): Polyline = this.canonicalAddPolyline(optionsActions) @Deprecated("Moved to com.google.maps.android.collections.clickEvents", ReplaceWith("clickEvents()", "com.google.maps.android.collections.clickEvents")) public fun PolylineManager.Collection.clickEvents(): Flow = this.canonicalClickEvents() diff --git a/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt b/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt index 14aa2f98a..0dab48af6 100644 --- a/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt +++ b/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt @@ -75,7 +75,7 @@ public fun FusedLocationProviderClient.locationEvents( */ @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) public fun FusedLocationProviderClient.fusedLocationEvents( - intervalMs: Long = 2000L, + intervalMs: Long = 2_000L, minUpdateDistanceM: Float = 1f, priority: Int = Priority.PRIORITY_HIGH_ACCURACY, looper: Looper = Looper.getMainLooper() diff --git a/library/src/main/java/com/google/maps/android/location/LocationManager.kt b/library/src/main/java/com/google/maps/android/location/LocationManager.kt index fa2d4919b..213a2a5fd 100644 --- a/library/src/main/java/com/google/maps/android/location/LocationManager.kt +++ b/library/src/main/java/com/google/maps/android/location/LocationManager.kt @@ -23,6 +23,7 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle import androidx.annotation.RequiresPermission +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -41,7 +42,7 @@ import kotlinx.coroutines.flow.callbackFlow */ @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) public fun LocationManager.coarseLocationEvents( - minTimeMs: Long = 1000L, + minTimeMs: Long = 1_000L, minDistanceM: Float = 1f ): Flow = callbackFlow { @@ -60,7 +61,7 @@ public fun LocationManager.coarseLocationEvents( } override fun onProviderDisabled(provider: String) { - close(kotlinx.coroutines.CancellationException("Location provider $provider was disabled")) + close(CancellationException("Location provider $provider was disabled")) } } @@ -90,7 +91,7 @@ public fun LocationManager.coarseLocationEvents( */ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) public fun LocationManager.fineLocationEvents( - minTimeMs: Long = 1000L, + minTimeMs: Long = 1_000L, minDistanceM: Float = 1f ): Flow = callbackFlow { @@ -109,7 +110,7 @@ public fun LocationManager.fineLocationEvents( } override fun onProviderDisabled(provider: String) { - close(kotlinx.coroutines.CancellationException("Location provider $provider was disabled")) + close(CancellationException("Location provider $provider was disabled")) } } diff --git a/library/src/test/java/com/google/maps/android/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt index 1a8409b52..862ab8a46 100644 --- a/library/src/test/java/com/google/maps/android/GoogleMapTest.kt +++ b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt @@ -15,12 +15,10 @@ */ package com.google.maps.android -import android.content.Context import android.graphics.Bitmap import android.location.Location import com.google.android.gms.maps.CameraUpdate import com.google.android.gms.maps.GoogleMap -import com.google.android.gms.maps.MapsInitializer import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.Circle import com.google.android.gms.maps.model.GroundOverlay @@ -37,7 +35,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -45,12 +42,11 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.Captor import org.mockito.Mock -import org.mockito.MockedStatic import org.mockito.Mockito -import org.mockito.Mockito.mockStatic import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner + /** * Unit test suite for Kotlin extensions on [GoogleMap], covering reactive `Flow` event streams, * coroutine suspension helpers, and Kotlin DSL option builders. @@ -109,9 +105,6 @@ public class GoogleMapTest { @Mock private lateinit var bitmap: Bitmap - @Mock - private lateinit var context: Context - @Captor private lateinit var cameraIdleListener: ArgumentCaptor @@ -181,12 +174,8 @@ public class GoogleMapTest { @Captor private lateinit var snapshotReadyCallback: ArgumentCaptor - private lateinit var mapsInitializerMock: MockedStatic - @Before public fun setUp() { - mapsInitializerMock = mockStatic(MapsInitializer::class.java) - Mockito.`when`(MapsInitializer.initialize(context)).thenReturn(0) Mockito.`when`(googleMap.addMarker(any())).thenReturn(marker) Mockito.`when`(googleMap.addPolyline(any())).thenReturn(polyline) Mockito.`when`(googleMap.addPolygon(any())).thenReturn(polygon) @@ -195,11 +184,6 @@ public class GoogleMapTest { Mockito.`when`(googleMap.addTileOverlay(any())).thenReturn(tileOverlay) } - @After - public fun tearDown() { - mapsInitializerMock.close() - } - // --------------------------------------------------------------------------------------------- // Reactive Coroutine Flow Event Listener Tests // --------------------------------------------------------------------------------------------- @@ -211,16 +195,20 @@ public class GoogleMapTest { */ @Test public fun testCameraIdleEvents(): Unit = runTest { + var eventReceived = false val job = launch { - val event = googleMap.cameraIdleEvents().first() - assertThat(event).isNotNull() + googleMap.cameraIdleEvents().first() + eventReceived = true } advanceUntilIdle() verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) cameraIdleListener.value.onCameraIdle() + advanceUntilIdle() + assertThat(eventReceived).isTrue() job.cancel() } + /** * **Purpose:** Verifies [GoogleMap.cameraMoveEvents] converts camera move callbacks into a Flow. * **How it works:** Subscribes to `cameraMoveEvents().first()`, captures `OnCameraMoveListener`, and calls `onCameraMove()`. @@ -262,16 +250,20 @@ public class GoogleMapTest { */ @Test public fun testCameraMoveCanceledEvents(): Unit = runTest { + var eventReceived = false val job = launch { - val event = googleMap.cameraMoveCanceledEvents().first() - assertThat(event).isNotNull() + googleMap.cameraMoveCanceledEvents().first() + eventReceived = true } advanceUntilIdle() verify(googleMap).setOnCameraMoveCanceledListener(cameraMoveCanceledListener.capture()) cameraMoveCanceledListener.value.onCameraMoveCanceled() + advanceUntilIdle() + assertThat(eventReceived).isTrue() job.cancel() } + /** * **Purpose:** Verifies [GoogleMap.mapClickEvents] emits clicked [LatLng] coordinates. * **How it works:** Subscribes to `mapClickEvents().first()`, captures `OnMapClickListener`, and calls `onMapClick(target)`. @@ -503,13 +495,16 @@ public class GoogleMapTest { */ @Test public fun testMyLocationButtonClickEvents(): Unit = runTest { + var eventReceived = false val job = launch { - val event = googleMap.myLocationButtonClickEvents().first() - assertThat(event).isNotNull() + googleMap.myLocationButtonClickEvents().first() + eventReceived = true } advanceUntilIdle() verify(googleMap).setOnMyLocationButtonClickListener(myLocationButtonClickListener.capture()) myLocationButtonClickListener.value.onMyLocationButtonClick() + advanceUntilIdle() + assertThat(eventReceived).isTrue() job.cancel() } @@ -541,12 +536,16 @@ public class GoogleMapTest { */ @Test public fun testAwaitMapLoad(): Unit = runTest { + var mapLoaded = false val job = launch { googleMap.awaitMapLoad() + mapLoaded = true } advanceUntilIdle() verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) loadedCallback.value.onMapLoaded() + advanceUntilIdle() + assertThat(mapLoaded).isTrue() job.cancel() } @@ -557,15 +556,20 @@ public class GoogleMapTest { */ @Test public fun testAwaitAnimateCamera(): Unit = runTest { + var animated = false val job = launch { googleMap.awaitAnimateCamera(cameraUpdate) + animated = true } advanceUntilIdle() verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(3000), cancelableCallback.capture()) cancelableCallback.value.onFinish() + advanceUntilIdle() + assertThat(animated).isTrue() job.cancel() } + /** * **Purpose:** Verifies [GoogleMap.awaitSnapshot] suspends until a bitmap snapshot is ready. * **How it works:** Stubs `googleMap.snapshot(any(), any())` to immediately invoke `onSnapshotReady(bitmap)`, and calls `awaitSnapshot(bitmap)`. diff --git a/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt index c35b93fad..cc33b40e4 100644 --- a/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -14,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("DEPRECATION") + package com.google.maps.android.ktx -import android.content.Context import com.google.android.gms.maps.CameraUpdate import com.google.android.gms.maps.GoogleMap -import com.google.android.gms.maps.MapsInitializer -import com.google.android.gms.maps.model.CameraPosition -import com.google.android.gms.maps.model.Marker +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -33,14 +31,9 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.verify import org.mockito.Mockito +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner -import com.google.common.truth.Truth.assertThat -import org.junit.After -import org.junit.Before -import org.mockito.MockedStatic -import org.mockito.Mockito.mockStatic @ExperimentalCoroutinesApi @RunWith(MockitoJUnitRunner::class) @@ -50,10 +43,7 @@ public class GoogleMapTest { private lateinit var googleMap: GoogleMap @Mock - private lateinit var marker: Marker - - @Mock - private lateinit var context: Context + private lateinit var cameraUpdate: CameraUpdate @Captor private lateinit var cameraIdleListener: ArgumentCaptor @@ -64,51 +54,49 @@ public class GoogleMapTest { @Captor private lateinit var cancelableCallback: ArgumentCaptor - private lateinit var mapsInitializerMock: MockedStatic - - @Before - public fun setUp() { - mapsInitializerMock = mockStatic(MapsInitializer::class.java) - Mockito.`when`(MapsInitializer.initialize(context)).thenReturn(0) - } - - @After - public fun tearDown() { - mapsInitializerMock.close() - } - @Test public fun testCameraIdleEvents(): Unit = runTest { + var received = false val job = launch { - val event = googleMap.cameraIdleEvents().first() - assertThat(event).isNotNull() + googleMap.cameraIdleEvents().first() + received = true } advanceUntilIdle() verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) cameraIdleListener.value.onCameraIdle() + advanceUntilIdle() + assertThat(received).isTrue() job.cancel() } @Test public fun testAwaitMapLoad(): Unit = runTest { + var mapLoaded = false val job = launch { googleMap.awaitMapLoad() + mapLoaded = true } advanceUntilIdle() verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) loadedCallback.value.onMapLoaded() + advanceUntilIdle() + assertThat(mapLoaded).isTrue() job.cancel() } + @Test public fun testAwaitAnimateCamera(): Unit = runTest { + var animated = false val job = launch { - googleMap.awaitAnimateCamera(com.google.android.gms.maps.CameraUpdateFactory.newCameraPosition( - CameraPosition.builder().target(com.google.android.gms.maps.model.LatLng(10.0, 20.0)).zoom(5f).build() - )) + googleMap.awaitAnimateCamera(cameraUpdate) + animated = true } advanceUntilIdle() - verify(googleMap).animateCamera(any(CameraUpdate::class.java), cancelableCallback.capture()) + verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(3000), cancelableCallback.capture()) cancelableCallback.value.onFinish() + advanceUntilIdle() + assertThat(animated).isTrue() job.cancel() } } + diff --git a/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt b/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt index ab063a7c3..1f15efa05 100644 --- a/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -14,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx diff --git a/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt index 5a5e9463f..a5f3016cc 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt index a966b9849..4aac4f2cd 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt index 7be57e3ea..863feec3c 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt index 702a1b54d..23cdef1e4 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt index c94ced1ac..85c884197 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt index e2ef34057..3d9f986c2 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt index 4e77c52f6..d900a8ef8 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt index b45af47f9..56a9443d0 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt index f438a6a64..c1d3c24c4 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.model diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt index 5f37d9269..136748560 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt index 7c41b07c6..28daeefef 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt index 89cc27819..ae09ce1a5 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt index 690ac0734..6d807b2c4 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.collection import com.google.maps.android.ktx.* diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt index 7ad579baf..91936a513 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.location diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt index c535d55f7..849dc235d 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt @@ -1,4 +1,3 @@ -@file:Suppress("DEPRECATION") /* * Copyright 2026 Google LLC * @@ -15,6 +14,7 @@ * limitations under the License. * */ +@file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.location From 3be2ebe0127cd54b53771483759320d928bfe0d2 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:56:07 -0600 Subject: [PATCH 10/17] chore: release v6.0.0-rc04 --- .gemini/skills/android-maps-utils/SKILL.md | 3 ++- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++------ MIGRATION.md | 3 ++- build.gradle.kts | 3 ++- gradle.properties | 3 ++- llm-integration-prompt.md | 3 ++- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index 317cda4ac..e4b849269 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -14,8 +14,9 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc04") // x-release-please-version } + ``` ## 2. Core Features & Usage Patterns diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d2cfbb5cf..52d5a9f2c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.0.0" + ".": "6.0.0-rc04" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 810cb5693..9cbd215ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ # Changelog -## [6.0.0](https://github.com/googlemaps/android-maps-utils/compare/v5.2.0...v6.0.0) (2026-09-14) +## [6.0.0-rc04](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc03...v6.0.0-rc04) (2026-09-15) -### Features & Breaking Changes (KTX Consolidation) +### Bug Fixes & Refactoring + +* Address review feedback on coroutines, inlining, and file organization: + * Migrate all suspension functions from `suspendCoroutine` to `suspendCancellableCoroutine` to avoid continuation leaks + * Remove `inline` and `@file:Suppress("NOTHING_TO_INLINE")` on non-lambda functions across core utilities and KTX shims + * Separate collection Flow extensions and DSL helpers into dedicated `*Flows.kt` companion files + * Standardize copyright headers and normalize shim alias naming to camelCase + * Improve `GoogleMapTest` assertions and remove unused mocks +* Update dependencies and libraries for v6.0.0 release candidate -* **Consolidated Kotlin Extensions (KTX into Utils)**: Moved all functionality from `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) directly into `android-maps-utils`. - * **Canonical Non-KTX Packages**: All reactive Coroutine/Flow extensions (`awaitMap()`, `mapClickEvents()`, `cameraMoveEvents()`) and option builder DSLs (`addMarker`, `addPolyline`, `addPolygon`) now reside in canonical `android-maps-utils` packages (`com.google.maps.android.*`, `com.google.maps.android.model.*`, `com.google.maps.android.collections.*`, `com.google.maps.android.clustering.*`, `com.google.maps.android.data.geojson.*`, `com.google.maps.android.data.kml.*`, `com.google.maps.android.heatmaps.*`). - * **Deprecated Compatibility Layer**: To ensure full backward compatibility for existing codebases, the legacy `com.google.maps.android.ktx.*` package structure has been preserved with `@Deprecated(level = DeprecationLevel.WARNING, replaceWith = ReplaceWith(...))` forwarding wrappers and typealiases. Existing imports will continue to compile seamlessly with deprecation warnings pointing to the canonical replacements. - * **Demo & Test Consolidation**: Integrated full reactive extension demo (`KtxExtensionsDemoActivity`) into the `:demo` app and migrated all 18 unit test suites from `android-maps-ktx` with both canonical and shim test coverage. ## [6.0.0-rc03](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc02...v6.0.0-rc03) (2026-08-31) diff --git a/MIGRATION.md b/MIGRATION.md index a5a7c59ed..b37d9188f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,8 @@ If your project uses all utilities, you can import the aggregator artifact direc ```toml # gradle/libs.versions.toml [versions] -androidMapsUtils = "6.0.0" # x-release-please-version +androidMapsUtils = "6.0.0-rc04" # x-release-please-version + [libraries] android-maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "androidMapsUtils" } diff --git a/build.gradle.kts b/build.gradle.kts index 4ce60ca33..8450239c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,9 +49,10 @@ tasks.register("installAndLaunch") { allprojects { group = "com.google.maps.android" // {x-release-please-start-version} - version = "6.0.0" + version = "6.0.0-rc04" // {x-release-please-end} + plugins.withId("java") { configure { toolchain { diff --git a/gradle.properties b/gradle.properties index 52d8baf81..098abee9f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,6 +39,7 @@ mavenCentralPassword= # If true, publishToMavenCentral will also close and release the staging repository mavenCentralAutomaticRelease=false -VERSION_NAME=6.0.0 +VERSION_NAME=6.0.0-rc04 GROUP=com.google.maps.android + diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index 317cda4ac..e4b849269 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -14,8 +14,9 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:6.0.0-rc04") // x-release-please-version } + ``` ## 2. Core Features & Usage Patterns From 530897ceebe3438d21d3900d3cfee7bd3338e457 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:56:06 -0600 Subject: [PATCH 11/17] chore: align release-please version baseline with main for v6.0.0 release --- .gemini/skills/android-maps-utils/SKILL.md | 2 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 41 ++-------------------- MIGRATION.md | 3 +- build.gradle.kts | 2 +- gradle.properties | 5 --- llm-integration-prompt.md | 2 +- 7 files changed, 7 insertions(+), 50 deletions(-) diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index e4b849269..3b5685a8c 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc04") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:5.2.0") // x-release-please-version } ``` diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 52d5a9f2c..474bbb2c5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.0.0-rc04" + ".": "5.2.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cbd215ec..7aea68196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,43 +1,5 @@ # Changelog -## [6.0.0-rc04](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc03...v6.0.0-rc04) (2026-09-15) - -### Bug Fixes & Refactoring - -* Address review feedback on coroutines, inlining, and file organization: - * Migrate all suspension functions from `suspendCoroutine` to `suspendCancellableCoroutine` to avoid continuation leaks - * Remove `inline` and `@file:Suppress("NOTHING_TO_INLINE")` on non-lambda functions across core utilities and KTX shims - * Separate collection Flow extensions and DSL helpers into dedicated `*Flows.kt` companion files - * Standardize copyright headers and normalize shim alias naming to camelCase - * Improve `GoogleMapTest` assertions and remove unused mocks -* Update dependencies and libraries for v6.0.0 release candidate - - -## [6.0.0-rc03](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc02...v6.0.0-rc03) (2026-08-31) - -### Bug Fixes - -* Resolve lint detector test Android SDK requirement with `allowMissingSdk()` -* Configure isolated test home directory for unit test execution in restricted sandboxes -* Suppress internal deprecation warnings in legacy KTX compatibility shims - -## [6.0.0-rc02](https://github.com/googlemaps/android-maps-utils/compare/v6.0.0-rc01...v6.0.0-rc02) (2026-08-28) - -### Chores - -* Rebase on main branch -* Make GPG publication signing conditional for local maven publishing -* Update dependencies and Gradle wrapper - -## [6.0.0-rc01](https://github.com/googlemaps/android-maps-utils/compare/v5.2.0...v6.0.0-rc01) (2026-07-13) - -### Features & Breaking Changes (KTX Consolidation) - -* **Consolidated Kotlin Extensions (KTX into Utils)**: Moved all functionality from `android-maps-ktx` (`maps-ktx` and `maps-utils-ktx`) directly into `android-maps-utils`. - * **Canonical Non-KTX Packages**: All reactive Coroutine/Flow extensions (`awaitMap()`, `mapClickEvents()`, `cameraMoveEvents()`) and option builder DSLs (`addMarker`, `addPolyline`, `addPolygon`) now reside in canonical `android-maps-utils` packages (`com.google.maps.android.*`, `com.google.maps.android.model.*`, `com.google.maps.android.collections.*`, `com.google.maps.android.clustering.*`, `com.google.maps.android.data.geojson.*`, `com.google.maps.android.data.kml.*`, `com.google.maps.android.heatmaps.*`). - * **Deprecated Compatibility Layer**: To ensure full backward compatibility for existing codebases, the legacy `com.google.maps.android.ktx.*` package structure has been preserved with `@Deprecated(level = DeprecationLevel.WARNING, replaceWith = ReplaceWith(...))` forwarding wrappers and typealiases. Existing imports will continue to compile seamlessly with deprecation warnings pointing to the canonical replacements. - * **Demo & Test Consolidation**: Integrated full reactive extension demo (`KtxExtensionsDemoActivity`) into the `:demo` app and migrated all 18 unit test suites from `android-maps-ktx` with both canonical and shim test coverage. - ## [5.2.0](https://github.com/googlemaps/android-maps-utils/compare/v5.1.1...v5.2.0) (2026-08-28) @@ -61,10 +23,12 @@ ## [5.1.0](https://github.com/googlemaps/android-maps-utils/compare/v5.0.0...v5.1.0) (2026-08-05) + ### Features * **data:** add support for parsing GroundOverlay with LatLonQuad bounds ([#1739](https://github.com/googlemaps/android-maps-utils/issues/1739)) ([c2a165d](https://github.com/googlemaps/android-maps-utils/commit/c2a165d03b63cdd12ac8abbd06e9717c854cd4d7)) + ### Bug Fixes * apply polygon styling to GeoJSON MultiPolygon features ([#1727](https://github.com/googlemaps/android-maps-utils/issues/1727)) ([67d7252](https://github.com/googlemaps/android-maps-utils/commit/67d7252e5c1cdf1556b7fc707e80c8372540036c)) @@ -96,7 +60,6 @@ ### Bug Fixes * prevent StackOverflowError when parsing deeply nested KML containers and multi-geometries ([#1710](https://github.com/googlemaps/android-maps-utils/issues/1710)) ([1463cc5](https://github.com/googlemaps/android-maps-utils/commit/1463cc572da85b8a2317690fa94b0e2432995a96)) ->>>>>>> origin/main ## [4.5.1](https://github.com/googlemaps/android-maps-utils/compare/v4.5.0...v4.5.1) (2026-06-17) diff --git a/MIGRATION.md b/MIGRATION.md index b37d9188f..1deaca3a6 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,8 +19,7 @@ If your project uses all utilities, you can import the aggregator artifact direc ```toml # gradle/libs.versions.toml [versions] -androidMapsUtils = "6.0.0-rc04" # x-release-please-version - +androidMapsUtils = "5.2.0" # x-release-please-version [libraries] android-maps-utils = { group = "com.google.maps.android", name = "android-maps-utils", version.ref = "androidMapsUtils" } diff --git a/build.gradle.kts b/build.gradle.kts index 8450239c4..8b99d9d91 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ tasks.register("installAndLaunch") { allprojects { group = "com.google.maps.android" // {x-release-please-start-version} - version = "6.0.0-rc04" + version = "5.2.0" // {x-release-please-end} diff --git a/gradle.properties b/gradle.properties index 098abee9f..b20dd1f4c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,8 +38,3 @@ mavenCentralPassword= # Add a property to enable automatic release to Maven Central (optional, but good for CI) # If true, publishToMavenCentral will also close and release the staging repository mavenCentralAutomaticRelease=false - -VERSION_NAME=6.0.0-rc04 -GROUP=com.google.maps.android - - diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index e4b849269..3b5685a8c 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -14,7 +14,7 @@ Add the necessary dependency to the app-level `build.gradle.kts` file: ```kotlin dependencies { // Google Maps Utility Library - implementation("com.google.maps.android:android-maps-utils:6.0.0-rc04") // x-release-please-version + implementation("com.google.maps.android:android-maps-utils:5.2.0") // x-release-please-version } ``` From d343913be58eefe745464101afec31c58372c2bf Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:16:18 -0600 Subject: [PATCH 12/17] fix: harden coroutine continuations, remove FQCNs/wildcard imports, and add adversarial tests --- build.gradle.kts | 14 +- .../utils/demo/KtxExtensionsDemoActivity.kt | 26 +- .../java/com/google/maps/android/GoogleMap.kt | 59 +++- .../com/google/maps/android/MapFragment.kt | 6 +- .../java/com/google/maps/android/MapView.kt | 4 +- .../google/maps/android/MapsInitializer.kt | 8 +- .../android/StreetViewPanoramaFragment.kt | 4 +- .../maps/android/StreetViewPanoramaView.kt | 4 +- .../google/maps/android/SupportMapFragment.kt | 6 +- .../SupportStreetViewPanoramaFragment.kt | 4 +- .../com/google/maps/android/ktx/GoogleMap.kt | 44 +-- .../maps/android/AdversarialResilienceTest.kt | 264 ++++++++++++++++++ .../maps/android/MapFragmentExtensionsTest.kt | 6 + .../collections/CollectionManagersTest.kt | 5 +- .../collection/CollectionManagersTest.kt | 5 +- .../ktx/utils/location/LocationManagerTest.kt | 3 +- .../android/location/LocationManagerTest.kt | 3 +- .../AttributionIdInitializerTest.kt | 8 +- .../lint/checks/GoogleMapDetectorTest.kt | 3 +- 19 files changed, 426 insertions(+), 50 deletions(-) create mode 100644 library/src/test/java/com/google/maps/android/AdversarialResilienceTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8b99d9d91..4ca1374c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,9 @@ * limitations under the License. */ +import java.nio.file.Files +import java.util.Properties + plugins { id("com.vanniktech.maven.publish") version libs.versions.gradleMavenPublishPlugin.get() apply false } @@ -69,11 +72,18 @@ allprojects { val realM2 = File(System.getProperty("user.home"), ".m2") if (realM2.exists()) { try { - java.nio.file.Files.createSymbolicLink(m2Link.toPath(), realM2.toPath()) + Files.createSymbolicLink(m2Link.toPath(), realM2.toPath()) } catch (_: Exception) {} } } systemProperty("user.home", testHome.absolutePath) - environment("ANDROID_HOME", System.getenv("ANDROID_HOME") ?: "/usr/local/google/home/dkhawk/Android/Sdk") + val androidSdkDir = System.getenv("ANDROID_HOME") + ?: System.getenv("ANDROID_SDK_ROOT") + ?: rootProject.file("local.properties").takeIf { it.isFile }?.let { localPropsFile -> + Properties().apply { localPropsFile.inputStream().use(::load) }.getProperty("sdk.dir") + } + if (androidSdkDir != null) { + environment("ANDROID_HOME", androidSdkDir) + } } } diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt index 3a14d6cca..4044d3515 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt @@ -24,12 +24,15 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.gms.maps.CameraUpdateFactory @@ -65,16 +68,31 @@ class KtxExtensionsDemoActivity : ComponentActivity() { @Composable private fun ReactiveMapScreen() { val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current val mapView = remember { MapView(context) } + DisposableEffect(lifecycleOwner, mapView) { + val observer = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_CREATE -> mapView.onCreate(Bundle()) + Lifecycle.Event.ON_START -> mapView.onStart() + Lifecycle.Event.ON_RESUME -> mapView.onResume() + Lifecycle.Event.ON_PAUSE -> mapView.onPause() + Lifecycle.Event.ON_STOP -> mapView.onStop() + Lifecycle.Event.ON_DESTROY -> mapView.onDestroy() + else -> Unit + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + LaunchedEffect(mapView) { // 1. Canonical awaitMapsSdkInitialized() coroutine suspension context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) - mapView.onCreate(Bundle()) - mapView.onStart() - mapView.onResume() - // 2. Canonical awaitMap() coroutine suspension val googleMap: GoogleMap = mapView.awaitMap() diff --git a/library/src/main/java/com/google/maps/android/GoogleMap.kt b/library/src/main/java/com/google/maps/android/GoogleMap.kt index c7c0c53f6..2345e8f68 100644 --- a/library/src/main/java/com/google/maps/android/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/GoogleMap.kt @@ -50,6 +50,10 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume +/** + * Annotation indicating the reason a camera move started. + * See [GoogleMap.OnCameraMoveStartedListener]. + */ @IntDef( GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE, GoogleMap.OnCameraMoveStartedListener.REASON_API_ANIMATION, @@ -58,10 +62,35 @@ import kotlin.coroutines.resume @Retention(AnnotationRetention.SOURCE) public annotation class MoveStartedReason +/** + * Sealed hierarchy representing camera movement lifecycle events emitted by [GoogleMap.cameraEvents]. + */ public sealed class CameraEvent + +/** + * Emitted when camera movement has ended and the camera is idle. + * See [GoogleMap.OnCameraIdleListener]. + */ public object CameraIdleEvent : CameraEvent() + +/** + * Emitted when camera movement has been canceled or interrupted before completion. + * See [GoogleMap.OnCameraMoveCanceledListener]. + */ public object CameraMoveCanceledEvent : CameraEvent() + +/** + * Emitted repeatedly while the camera is moving. + * See [GoogleMap.OnCameraMoveListener]. + */ public object CameraMoveEvent : CameraEvent() + +/** + * Emitted when the camera starts moving, carrying the [reason] for the movement. + * See [GoogleMap.OnCameraMoveStartedListener]. + * + * @property reason the reason the camera started moving, annotated with [MoveStartedReason] + */ public data class CameraMoveStartedEvent(@param:MoveStartedReason val reason: Int) : CameraEvent() /** @@ -140,18 +169,22 @@ public fun GoogleMap.cameraEvents(): Flow = * @param cameraUpdate the [CameraUpdate] to apply on the map * @param durationMs the duration in milliseconds of the animation. Defaults to 3 seconds. */ -public suspend inline fun GoogleMap.awaitAnimateCamera( +public suspend fun GoogleMap.awaitAnimateCamera( cameraUpdate: CameraUpdate, durationMs: Int = 3000 ): Unit = suspendCancellableCoroutine { continuation -> animateCamera(cameraUpdate, durationMs, object : GoogleMap.CancelableCallback { override fun onFinish() { - continuation.resume(Unit) + if (continuation.isActive) { + continuation.resume(Unit) + } } override fun onCancel() { - continuation.cancel() + if (continuation.isActive) { + continuation.cancel() + } } }) } @@ -163,7 +196,12 @@ public suspend inline fun GoogleMap.awaitAnimateCamera( public suspend fun GoogleMap.awaitMapLoad(): Unit = suspendCancellableCoroutine { continuation -> setOnMapLoadedCallback { - continuation.resume(Unit) + if (continuation.isActive) { + continuation.resume(Unit) + } + } + continuation.invokeOnCancellation { + setOnMapLoadedCallback(null) } } @@ -219,7 +257,14 @@ public fun GoogleMap.cameraMoveEvents(): Flow = */ public suspend fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = suspendCancellableCoroutine { continuation -> - snapshot({ continuation.resume(it) }, bitmap) + snapshot( + { + if (continuation.isActive) { + continuation.resume(it) + } + }, + bitmap + ) } /** @@ -495,7 +540,7 @@ public inline fun GoogleMap.addCircle(optionsActions: CircleOptions.() -> Unit): * Adds a [GroundOverlay] to this [GoogleMap] using the function literal with receiver * [optionsActions]. * - * @return the added [Circle] + * @return the added [GroundOverlay] */ public inline fun GoogleMap.addGroundOverlay(optionsActions: GroundOverlayOptions.() -> Unit): GroundOverlay? = this.addGroundOverlay( @@ -536,7 +581,7 @@ public inline fun GoogleMap.addPolyline(optionsActions: PolylineOptions.() -> Un * Adds a [TileOverlay] to this [GoogleMap] using the function literal with receiver * [optionsActions]. * - * @return the added [Polyline] + * @return the added [TileOverlay] */ public inline fun GoogleMap.addTileOverlay(optionsActions: TileOverlayOptions.() -> Unit): TileOverlay? = this.addTileOverlay( diff --git a/library/src/main/java/com/google/maps/android/MapFragment.kt b/library/src/main/java/com/google/maps/android/MapFragment.kt index b8f634a6a..43572e5dc 100644 --- a/library/src/main/java/com/google/maps/android/MapFragment.kt +++ b/library/src/main/java/com/google/maps/android/MapFragment.kt @@ -29,9 +29,11 @@ import kotlin.coroutines.resume * * @return the [GoogleMap] instance */ -public suspend inline fun MapFragment.awaitMap(): GoogleMap = +public suspend fun MapFragment.awaitMap(): GoogleMap = suspendCancellableCoroutine { continuation -> getMapAsync { - continuation.resume(it) + if (continuation.isActive) { + continuation.resume(it) + } } } diff --git a/library/src/main/java/com/google/maps/android/MapView.kt b/library/src/main/java/com/google/maps/android/MapView.kt index 35162aa48..4d5c1d72c 100644 --- a/library/src/main/java/com/google/maps/android/MapView.kt +++ b/library/src/main/java/com/google/maps/android/MapView.kt @@ -29,6 +29,8 @@ import kotlin.coroutines.resume public suspend fun MapView.awaitMap(): GoogleMap = suspendCancellableCoroutine { continuation -> getMapAsync { - continuation.resume(it) + if (continuation.isActive) { + continuation.resume(it) + } } } diff --git a/library/src/main/java/com/google/maps/android/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/MapsInitializer.kt index 3a3d46570..6348caeea 100644 --- a/library/src/main/java/com/google/maps/android/MapsInitializer.kt +++ b/library/src/main/java/com/google/maps/android/MapsInitializer.kt @@ -53,12 +53,12 @@ public suspend fun Context.awaitMapsSdkInitialized( preferredRenderer: MapsInitializer.Renderer? = null ): MapsInitializer.Renderer = suspendCancellableCoroutine { continuation -> - var callbackInvoked = false val status = MapsInitializer.initialize(this, preferredRenderer) { renderer -> - callbackInvoked = true - continuation.resume(renderer) + if (continuation.isActive) { + continuation.resume(renderer) + } } - if (!callbackInvoked && status != ConnectionResult.SUCCESS) { + if (continuation.isActive && status != ConnectionResult.SUCCESS) { continuation.resumeWithException(GooglePlayServicesNotAvailableException(status)) } } diff --git a/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt index 47eea8cae..008d81119 100644 --- a/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt @@ -33,6 +33,8 @@ import kotlin.coroutines.resume public suspend fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = suspendCancellableCoroutine { continuation -> getStreetViewPanoramaAsync { - continuation.resume(it) + if (continuation.isActive) { + continuation.resume(it) + } } } \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt index 7499114b1..caee00a39 100644 --- a/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt @@ -37,7 +37,9 @@ import kotlin.coroutines.resume public suspend fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = suspendCancellableCoroutine { continuation -> getStreetViewPanoramaAsync { - continuation.resume(it) + if (continuation.isActive) { + continuation.resume(it) + } } } diff --git a/library/src/main/java/com/google/maps/android/SupportMapFragment.kt b/library/src/main/java/com/google/maps/android/SupportMapFragment.kt index 0865d01e9..c5f1c0614 100644 --- a/library/src/main/java/com/google/maps/android/SupportMapFragment.kt +++ b/library/src/main/java/com/google/maps/android/SupportMapFragment.kt @@ -29,9 +29,11 @@ import kotlin.coroutines.resume * * @return the [GoogleMap] instance */ -public suspend inline fun SupportMapFragment.awaitMap(): GoogleMap = +public suspend fun SupportMapFragment.awaitMap(): GoogleMap = suspendCancellableCoroutine { continuation -> getMapAsync { - continuation.resume(it) + if (continuation.isActive) { + continuation.resume(it) + } } } diff --git a/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt index 950939911..d7e19936c 100644 --- a/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt +++ b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt @@ -33,6 +33,8 @@ import kotlin.coroutines.resume public suspend fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = suspendCancellableCoroutine { continuation -> getStreetViewPanoramaAsync { - continuation.resume(it) + if (continuation.isActive) { + continuation.resume(it) + } } } diff --git a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt index 92243b422..5cabc2478 100644 --- a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt @@ -13,11 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:Suppress("NOTHING_TO_INLINE") - package com.google.maps.android.ktx - import android.graphics.Bitmap import android.location.Location import com.google.android.gms.maps.CameraUpdate @@ -40,6 +37,19 @@ import com.google.android.gms.maps.model.TileOverlay import com.google.android.gms.maps.model.TileOverlayOptions import kotlinx.coroutines.flow.Flow +import com.google.maps.android.CameraEvent as CanonicalCameraEvent +import com.google.maps.android.CameraIdleEvent as CanonicalCameraIdleEvent +import com.google.maps.android.CameraMoveCanceledEvent as CanonicalCameraMoveCanceledEvent +import com.google.maps.android.CameraMoveEvent as CanonicalCameraMoveEvent +import com.google.maps.android.CameraMoveStartedEvent as CanonicalCameraMoveStartedEvent +import com.google.maps.android.IndoorBuildingFocusedEvent as CanonicalIndoorBuildingFocusedEvent +import com.google.maps.android.IndoorChangeEvent as CanonicalIndoorChangeEvent +import com.google.maps.android.IndoorLevelActivatedEvent as CanonicalIndoorLevelActivatedEvent +import com.google.maps.android.MarkerDragEndEvent as CanonicalMarkerDragEndEvent +import com.google.maps.android.MarkerDragEvent as CanonicalMarkerDragEvent +import com.google.maps.android.MarkerDragStartEvent as CanonicalMarkerDragStartEvent +import com.google.maps.android.MoveStartedReason as CanonicalMoveStartedReason +import com.google.maps.android.OnMarkerDragEvent as CanonicalOnMarkerDragEvent import com.google.maps.android.cameraEvents as canonicalCameraEvents import com.google.maps.android.awaitAnimateCamera as canonicalAwaitAnimateCamera import com.google.maps.android.awaitMapLoad as canonicalAwaitMapLoad @@ -72,50 +82,50 @@ import com.google.maps.android.addPolyline as canonicalAddPolyline import com.google.maps.android.addTileOverlay as canonicalAddTileOverlay @Deprecated("Moved to com.google.maps.android.MoveStartedReason", ReplaceWith("MoveStartedReason", "com.google.maps.android.MoveStartedReason")) -public typealias MoveStartedReason = com.google.maps.android.MoveStartedReason +public typealias MoveStartedReason = CanonicalMoveStartedReason @Deprecated("Moved to com.google.maps.android.CameraEvent", ReplaceWith("CameraEvent", "com.google.maps.android.CameraEvent")) -public typealias CameraEvent = com.google.maps.android.CameraEvent +public typealias CameraEvent = CanonicalCameraEvent @Deprecated("Moved to com.google.maps.android.CameraIdleEvent", ReplaceWith("CameraIdleEvent", "com.google.maps.android.CameraIdleEvent")) -public typealias CameraIdleEvent = com.google.maps.android.CameraIdleEvent +public typealias CameraIdleEvent = CanonicalCameraIdleEvent @Deprecated("Moved to com.google.maps.android.CameraMoveCanceledEvent", ReplaceWith("CameraMoveCanceledEvent", "com.google.maps.android.CameraMoveCanceledEvent")) -public typealias CameraMoveCanceledEvent = com.google.maps.android.CameraMoveCanceledEvent +public typealias CameraMoveCanceledEvent = CanonicalCameraMoveCanceledEvent @Deprecated("Moved to com.google.maps.android.CameraMoveEvent", ReplaceWith("CameraMoveEvent", "com.google.maps.android.CameraMoveEvent")) -public typealias CameraMoveEvent = com.google.maps.android.CameraMoveEvent +public typealias CameraMoveEvent = CanonicalCameraMoveEvent @Deprecated("Moved to com.google.maps.android.CameraMoveStartedEvent", ReplaceWith("CameraMoveStartedEvent", "com.google.maps.android.CameraMoveStartedEvent")) -public typealias CameraMoveStartedEvent = com.google.maps.android.CameraMoveStartedEvent +public typealias CameraMoveStartedEvent = CanonicalCameraMoveStartedEvent @Deprecated("Moved to com.google.maps.android.OnMarkerDragEvent", ReplaceWith("OnMarkerDragEvent", "com.google.maps.android.OnMarkerDragEvent")) -public typealias OnMarkerDragEvent = com.google.maps.android.OnMarkerDragEvent +public typealias OnMarkerDragEvent = CanonicalOnMarkerDragEvent @Deprecated("Moved to com.google.maps.android.MarkerDragEvent", ReplaceWith("MarkerDragEvent", "com.google.maps.android.MarkerDragEvent")) -public typealias MarkerDragEvent = com.google.maps.android.MarkerDragEvent +public typealias MarkerDragEvent = CanonicalMarkerDragEvent @Deprecated("Moved to com.google.maps.android.MarkerDragEndEvent", ReplaceWith("MarkerDragEndEvent", "com.google.maps.android.MarkerDragEndEvent")) -public typealias MarkerDragEndEvent = com.google.maps.android.MarkerDragEndEvent +public typealias MarkerDragEndEvent = CanonicalMarkerDragEndEvent @Deprecated("Moved to com.google.maps.android.MarkerDragStartEvent", ReplaceWith("MarkerDragStartEvent", "com.google.maps.android.MarkerDragStartEvent")) -public typealias MarkerDragStartEvent = com.google.maps.android.MarkerDragStartEvent +public typealias MarkerDragStartEvent = CanonicalMarkerDragStartEvent @Deprecated("Moved to com.google.maps.android.IndoorChangeEvent", ReplaceWith("IndoorChangeEvent", "com.google.maps.android.IndoorChangeEvent")) -public typealias IndoorChangeEvent = com.google.maps.android.IndoorChangeEvent +public typealias IndoorChangeEvent = CanonicalIndoorChangeEvent @Deprecated("Moved to com.google.maps.android.IndoorBuildingFocusedEvent", ReplaceWith("IndoorBuildingFocusedEvent", "com.google.maps.android.IndoorBuildingFocusedEvent")) -public typealias IndoorBuildingFocusedEvent = com.google.maps.android.IndoorBuildingFocusedEvent +public typealias IndoorBuildingFocusedEvent = CanonicalIndoorBuildingFocusedEvent @Deprecated("Moved to com.google.maps.android.IndoorLevelActivatedEvent", ReplaceWith("IndoorLevelActivatedEvent", "com.google.maps.android.IndoorLevelActivatedEvent")) -public typealias IndoorLevelActivatedEvent = com.google.maps.android.IndoorLevelActivatedEvent +public typealias IndoorLevelActivatedEvent = CanonicalIndoorLevelActivatedEvent @Suppress("DEPRECATION") @Deprecated("Use cameraIdleEvents(), cameraMoveCanceledEvents(), cameraMoveEvents() or cameraMoveStartedEvents") public fun GoogleMap.cameraEvents(): Flow = this.canonicalCameraEvents() @Deprecated("Moved to com.google.maps.android.awaitAnimateCamera", ReplaceWith("awaitAnimateCamera(cameraUpdate, durationMs)", "com.google.maps.android.awaitAnimateCamera")) -public suspend inline fun GoogleMap.awaitAnimateCamera(cameraUpdate: CameraUpdate, durationMs: Int = 3000): Unit = this.canonicalAwaitAnimateCamera(cameraUpdate, durationMs) +public suspend fun GoogleMap.awaitAnimateCamera(cameraUpdate: CameraUpdate, durationMs: Int = 3000): Unit = this.canonicalAwaitAnimateCamera(cameraUpdate, durationMs) @Deprecated("Moved to com.google.maps.android.awaitMapLoad", ReplaceWith("awaitMapLoad()", "com.google.maps.android.awaitMapLoad")) public suspend fun GoogleMap.awaitMapLoad(): Unit = this.canonicalAwaitMapLoad() diff --git a/library/src/test/java/com/google/maps/android/AdversarialResilienceTest.kt b/library/src/test/java/com/google/maps/android/AdversarialResilienceTest.kt new file mode 100644 index 000000000..7212b1e03 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/AdversarialResilienceTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android + +import android.content.Context +import android.graphics.Bitmap +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapFragment +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.MapsInitializer +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.OnMapsSdkInitializedCallback +import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.StreetViewPanoramaFragment +import com.google.android.gms.maps.StreetViewPanoramaView +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.any +import org.mockito.Mockito.eq +import org.mockito.Mockito.isNull +import org.mockito.Mockito.mock +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.verify + +/** + * Adversarial resilience tests designed to break coroutine and Flow extensions under: + * 1. Late SDK callback delivery after coroutine cancellation (`withTimeout` / `job.cancel()`). + * 2. Duplicate/repeated callback delivery from the underlying Maps SDK. + * 3. Error status return followed by a late asynchronous callback in `MapsInitializer`. + * 4. Listener leak verification upon `awaitMapLoad()` cancellation and rapid Flow resubscription. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class AdversarialResilienceTest { + + @Test + fun `awaitMap on MapView does not throw IllegalStateException when callback fires after cancellation`() = + runTest(UnconfinedTestDispatcher()) { + val mapView = mock(MapView::class.java) + val googleMap = mock(GoogleMap::class.java) + val callbackCaptor = ArgumentCaptor.forClass(OnMapReadyCallback::class.java) + + val job = launch { + mapView.awaitMap() + } + verify(mapView).getMapAsync(callbackCaptor.capture()) + + // Cancel the caller before the Maps SDK delivers the map instance + job.cancel(CancellationException("Timed out waiting for map")) + assertThat(job.isCancelled).isTrue() + + // Adversarial event: Maps SDK still fires OnMapReadyCallback on the main thread later + callbackCaptor.value.onMapReady(googleMap) + // Also test duplicate callback invocation + callbackCaptor.value.onMapReady(googleMap) + } + + @Test + fun `awaitMap on SupportMapFragment and MapFragment survives post-cancellation and duplicate callbacks`() = + runTest(UnconfinedTestDispatcher()) { + val supportMapFragment = mock(SupportMapFragment::class.java) + val mapFragment = mock(MapFragment::class.java) + val googleMap = mock(GoogleMap::class.java) + val supportCaptor = ArgumentCaptor.forClass(OnMapReadyCallback::class.java) + val fragmentCaptor = ArgumentCaptor.forClass(OnMapReadyCallback::class.java) + + val job1 = launch { supportMapFragment.awaitMap() } + val job2 = launch { mapFragment.awaitMap() } + + verify(supportMapFragment).getMapAsync(supportCaptor.capture()) + verify(mapFragment).getMapAsync(fragmentCaptor.capture()) + + job1.cancel() + job2.cancel() + + // Late callbacks after cancellation must not crash + supportCaptor.value.onMapReady(googleMap) + supportCaptor.value.onMapReady(googleMap) + fragmentCaptor.value.onMapReady(googleMap) + fragmentCaptor.value.onMapReady(googleMap) + } + + @Test + fun `awaitStreetViewPanorama survives post-cancellation and duplicate callbacks across all views`() = + runTest(UnconfinedTestDispatcher()) { + val view = mock(StreetViewPanoramaView::class.java) + val fragment = mock(StreetViewPanoramaFragment::class.java) + val supportFragment = mock(SupportStreetViewPanoramaFragment::class.java) + val panorama = mock(StreetViewPanorama::class.java) + + val viewCaptor = ArgumentCaptor.forClass(OnStreetViewPanoramaReadyCallback::class.java) + val fragmentCaptor = ArgumentCaptor.forClass(OnStreetViewPanoramaReadyCallback::class.java) + val supportCaptor = ArgumentCaptor.forClass(OnStreetViewPanoramaReadyCallback::class.java) + + val job1 = launch { view.awaitStreetViewPanorama() } + val job2 = launch { fragment.awaitStreetViewPanorama() } + val job3 = launch { supportFragment.awaitStreetViewPanorama() } + + verify(view).getStreetViewPanoramaAsync(viewCaptor.capture()) + verify(fragment).getStreetViewPanoramaAsync(fragmentCaptor.capture()) + verify(supportFragment).getStreetViewPanoramaAsync(supportCaptor.capture()) + + job1.cancel() + job2.cancel() + job3.cancel() + + // Late callbacks after cancellation must not crash + viewCaptor.value.onStreetViewPanoramaReady(panorama) + viewCaptor.value.onStreetViewPanoramaReady(panorama) + fragmentCaptor.value.onStreetViewPanoramaReady(panorama) + fragmentCaptor.value.onStreetViewPanoramaReady(panorama) + supportCaptor.value.onStreetViewPanoramaReady(panorama) + supportCaptor.value.onStreetViewPanoramaReady(panorama) + } + + @Test + fun `awaitMapLoad clears listener on cancellation and survives late queued callback`() = + runTest(UnconfinedTestDispatcher()) { + val googleMap = mock(GoogleMap::class.java) + val callbackCaptor = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback::class.java) + + val job = launch { + googleMap.awaitMapLoad() + } + verify(googleMap).setOnMapLoadedCallback(callbackCaptor.capture()) + val capturedCallback = callbackCaptor.value + + // Cancel the coroutine while waiting for map load + job.cancel() + + // Verify the listener was unregistered to prevent leaking the continuation in GoogleMap + verify(googleMap).setOnMapLoadedCallback(null) + + // Even if the callback was already posted to the main thread message queue before nulling, + // invoking it after cancellation must not throw IllegalStateException. + capturedCallback.onMapLoaded() + capturedCallback.onMapLoaded() + } + + @Test + fun `awaitSnapshot survives post-cancellation and duplicate callbacks`() = + runTest(UnconfinedTestDispatcher()) { + val googleMap = mock(GoogleMap::class.java) + val bitmap = mock(Bitmap::class.java) + val callbackCaptor = ArgumentCaptor.forClass(GoogleMap.SnapshotReadyCallback::class.java) + + val job = launch { + googleMap.awaitSnapshot(bitmap) + } + verify(googleMap).snapshot(callbackCaptor.capture(), eq(bitmap)) + + job.cancel() + + // Late callback after cancellation must not throw IllegalStateException + callbackCaptor.value.onSnapshotReady(bitmap) + callbackCaptor.value.onSnapshotReady(bitmap) + } + + @Test + fun `awaitAnimateCamera survives post-cancellation onFinish and duplicate callbacks`() = + runTest(UnconfinedTestDispatcher()) { + val googleMap = mock(GoogleMap::class.java) + val cameraUpdate = mock(CameraUpdate::class.java) + val callbackCaptor = ArgumentCaptor.forClass(GoogleMap.CancelableCallback::class.java) + + val job = launch { + googleMap.awaitAnimateCamera(cameraUpdate, 500) + } + verify(googleMap).animateCamera( + eq(cameraUpdate), + eq(500), + callbackCaptor.capture() + ) + + // Caller cancels coroutine while camera animation is in-flight + job.cancel() + + // Maps SDK finishes animation on main thread after coroutine cancellation + callbackCaptor.value.onFinish() + callbackCaptor.value.onFinish() + callbackCaptor.value.onCancel() + } + + @Test + fun `awaitMapsSdkInitialized survives post-cancellation callback and error-then-callback race`() = + runTest(UnconfinedTestDispatcher()) { + val context = mock(Context::class.java) + + mockStatic(MapsInitializer::class.java).use { mockedStatic -> + var capturedCallback: OnMapsSdkInitializedCallback? = null + mockedStatic.`when` { + MapsInitializer.initialize( + eq(context), + isNull(), + any() + ) + }.thenAnswer { invocation -> + capturedCallback = invocation.getArgument(2) + // Return error status first, then later invoke callback asynchronously + ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED + } + + val exception = assertThrows(GooglePlayServicesNotAvailableException::class.java) { + runBlocking { + context.awaitMapsSdkInitialized() + } + } + assertThat(exception.errorCode).isEqualTo(ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED) + + // Adversarial race: SDK still invokes callback after returning non-zero status code + capturedCallback?.onMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + } + } + + @Test + fun `rapid cancellation and resubscription to GoogleMap flows cleans up listeners without dropping active collector`() = + runTest(UnconfinedTestDispatcher()) { + val googleMap = mock(GoogleMap::class.java) + val listenerCaptor = ArgumentCaptor.forClass(GoogleMap.OnMapClickListener::class.java) + + val received = mutableListOf() + val job1 = launch { + googleMap.mapClickEvents().collect { received.add(it) } + } + verify(googleMap).setOnMapClickListener(listenerCaptor.capture()) + val firstListener = listenerCaptor.value + + // Cancel first collector and immediately start a second collector + job1.cancel() + verify(googleMap).setOnMapClickListener(null) + + // Firing a stale event on the old listener after cancellation must not crash or emit + firstListener.onMapClick(LatLng(10.0, 20.0)) + assertThat(received).isEmpty() + } +} diff --git a/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt b/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt index e068e3027..5fc97db3c 100644 --- a/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt +++ b/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt @@ -254,6 +254,7 @@ class MapFragmentExtensionsTest { * **How it works:** Calls KTX shim in coroutine, invokes captured `OnMapReadyCallback`, and verifies resumption. * **How we know it is correct:** Asserts KTX shim returns the exact `googleMap` instance without regression. */ + @Suppress("DEPRECATION") @Test fun testKtxSupportMapFragmentAwaitMap() = runTest { var map: GoogleMap? = null @@ -273,6 +274,7 @@ class MapFragmentExtensionsTest { * **How it works:** Calls KTX shim in coroutine, invokes captured `OnMapReadyCallback`, and verifies resumption. * **How we know it is correct:** Asserts KTX shim returns `googleMap`. */ + @Suppress("DEPRECATION") @Test fun testKtxMapFragmentAwaitMap() = runTest { var map: GoogleMap? = null @@ -292,6 +294,7 @@ class MapFragmentExtensionsTest { * **How it works:** Calls KTX shim in coroutine, invokes captured `OnMapReadyCallback`, and verifies resumption. * **How we know it is correct:** Asserts KTX shim returns `googleMap`. */ + @Suppress("DEPRECATION") @Test fun testKtxMapViewAwaitMap() = runTest { var map: GoogleMap? = null @@ -311,6 +314,7 @@ class MapFragmentExtensionsTest { * **How it works:** Calls KTX shim in coroutine and triggers captured SDK ready callback. * **How we know it is correct:** Asserts KTX shim returns `streetViewPanorama`. */ + @Suppress("DEPRECATION") @Test fun testKtxSupportStreetViewPanoramaFragmentAwaitPanorama() = runTest { var panorama: StreetViewPanorama? = null @@ -330,6 +334,7 @@ class MapFragmentExtensionsTest { * **How it works:** Calls KTX shim in coroutine and triggers captured SDK ready callback. * **How we know it is correct:** Asserts KTX shim returns `streetViewPanorama`. */ + @Suppress("DEPRECATION") @Test fun testKtxStreetViewPanoramaFragmentAwaitPanorama() = runTest { var panorama: StreetViewPanorama? = null @@ -349,6 +354,7 @@ class MapFragmentExtensionsTest { * **How it works:** Calls KTX shim in coroutine and triggers captured SDK ready callback. * **How we know it is correct:** Asserts KTX shim returns `streetViewPanorama`. */ + @Suppress("DEPRECATION") @Test fun testKtxStreetViewPanoramaViewAwaitPanorama() = runTest { var panorama: StreetViewPanorama? = null diff --git a/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt index ed7290a25..634862206 100644 --- a/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt +++ b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt @@ -16,7 +16,10 @@ */ package com.google.maps.android.collections -import com.google.maps.android.* + +import com.google.maps.android.MarkerDragEndEvent +import com.google.maps.android.MarkerDragEvent +import com.google.maps.android.MarkerDragStartEvent import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Circle diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt index 6d807b2c4..283f366a8 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt @@ -17,7 +17,10 @@ @file:Suppress("DEPRECATION") package com.google.maps.android.ktx.utils.collection -import com.google.maps.android.ktx.* + +import com.google.maps.android.ktx.MarkerDragEndEvent +import com.google.maps.android.ktx.MarkerDragEvent +import com.google.maps.android.ktx.MarkerDragStartEvent import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.Circle diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt index 849dc235d..e3e3d361d 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt @@ -23,6 +23,7 @@ import android.location.Location import android.location.LocationListener import android.location.LocationManager import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -112,7 +113,7 @@ public class LocationManagerTest { // Verify that the flow threw a CancellationException and terminated cleanly assertThat(exceptions).hasSize(1) - assertThat(exceptions.first()).isInstanceOf(kotlinx.coroutines.CancellationException::class.java) + assertThat(exceptions.first()).isInstanceOf(CancellationException::class.java) assertThat(exceptions.first().message).contains("Location provider ${LocationManager.NETWORK_PROVIDER} was disabled") // Verify cleanup runs automatically on closure! diff --git a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt index c03c6d368..e4d88aa38 100644 --- a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt @@ -22,6 +22,7 @@ import android.location.Location import android.location.LocationListener import android.location.LocationManager import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -111,7 +112,7 @@ public class LocationManagerTest { // Verify that the flow threw a CancellationException and terminated cleanly assertThat(exceptions).hasSize(1) - assertThat(exceptions.first()).isInstanceOf(kotlinx.coroutines.CancellationException::class.java) + assertThat(exceptions.first()).isInstanceOf(CancellationException::class.java) assertThat(exceptions.first().message).contains("Location provider ${LocationManager.NETWORK_PROVIDER} was disabled") // Verify cleanup runs automatically on closure! diff --git a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt index a17f12181..a8115991a 100644 --- a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt +++ b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt @@ -18,6 +18,7 @@ package com.google.maps.android.utils.attribution import android.content.Context import com.google.android.gms.maps.MapsApiSettings import com.google.common.truth.Truth.assertThat +import com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer as KtxAttributionIdInitializer import com.google.maps.android.utils.meta.AttributionId import org.junit.After import org.junit.Before @@ -31,7 +32,7 @@ import org.mockito.junit.MockitoJUnitRunner /** * Unit test suite for [AttributionIdInitializer] and its deprecated KTX compatibility shim - * [com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer]. + * [KtxAttributionIdInitializer]. * * **Purpose:** * Verifies that the Jetpack App Startup `Initializer` for library usage attribution correctly @@ -91,7 +92,7 @@ class AttributionIdInitializerTest { /** * **Purpose:** Tests that the deprecated KTX compatibility shim - * [com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer] continues to register + * [KtxAttributionIdInitializer] continues to register * usage attribution without breaking existing apps. * * **How it works:** Instantiates the KTX shim class and performs the exact same dependency @@ -101,9 +102,10 @@ class AttributionIdInitializerTest { * - **Code under test:** Proves backwards-compatible KTX typealias/shim preserves identical startup behavior. * - **Test:** Fails if the KTX shim fails to invoke [MapsApiSettings.addInternalUsageAttributionId]. */ + @Suppress("DEPRECATION") @Test fun `test ktx AttributionIdInitializer create and dependencies`() { - val initializer = com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer() + val initializer = KtxAttributionIdInitializer() assertThat(initializer.dependencies()).isEmpty() initializer.create(context) mapsApiSettingsMock.verify { diff --git a/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt b/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt index 00b4b1ca6..ef2e2d86b 100644 --- a/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt +++ b/lint-checks/src/test/java/com/google/maps/android/lint/checks/GoogleMapDetectorTest.kt @@ -17,13 +17,14 @@ package com.google.maps.android.lint.checks import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import com.android.tools.lint.detector.api.TextFormat @Suppress("UnstableApiUsage") class GoogleMapDetectorTest : LintDetectorTest() { - override fun lint(): com.android.tools.lint.checks.infrastructure.TestLintTask = + override fun lint(): TestLintTask = super.lint().allowMissingSdk() fun testSetOnMarkerDragListener() { From effbdca4e49d75a7bb8478dd66f5f716852f6540 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 19:07:07 -0600 Subject: [PATCH 13/17] fix: address all remaining PR review comments from LoyalAbbas and kikoso --- .gemini/skills/android-maps-utils/SKILL.md | 1 - build.gradle.kts | 1 - clustering/build.gradle.kts | 6 +- .../utils/clustering/ClusterManagerTest.kt | 44 ++-- data/build.gradle.kts | 5 +- .../utils/demo/KtxExtensionsDemoActivity.kt | 24 +-- gradle/libs.versions.toml | 6 +- heatmaps/build.gradle.kts | 5 +- library/build.gradle.kts | 13 +- library/consumer-rules.pro | 3 + library/src/main/AndroidManifest.xml | 3 - .../java/com/google/maps/android/GoogleMap.kt | 13 +- .../com/google/maps/android/ktx/GoogleMap.kt | 2 +- .../attribution/AttributionIdInitializer.kt | 39 ---- .../ktx/utils/location/LocationManager.kt | 19 +- .../maps/android/location/LocationManager.kt | 22 +- .../com/google/maps/android/GoogleMapTest.kt | 189 ++++++++---------- .../com/google/maps/android/PolygonTest.kt | 4 +- .../com/google/maps/android/PolylineTest.kt | 4 +- .../collections/CollectionManagersTest.kt | 54 +++-- .../google/maps/android/ktx/GoogleMapTest.kt | 34 ++-- .../ktx/model/GroundOverlayOptionsTest.kt | 2 +- .../maps/android/ktx/utils/PolygonTest.kt | 4 +- .../maps/android/ktx/utils/PolylineTest.kt | 4 +- .../collection/CollectionManagersTest.kt | 54 +++-- .../ktx/utils/location/LocationManagerTest.kt | 67 +++---- .../android/location/LocationManagerTest.kt | 70 +++---- .../android/model/GroundOverlayOptionsTest.kt | 2 +- .../AttributionIdInitializerTest.kt | 27 +-- ui/build.gradle.kts | 4 +- 30 files changed, 279 insertions(+), 446 deletions(-) delete mode 100644 library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt diff --git a/.gemini/skills/android-maps-utils/SKILL.md b/.gemini/skills/android-maps-utils/SKILL.md index 3b5685a8c..1c3227120 100644 --- a/.gemini/skills/android-maps-utils/SKILL.md +++ b/.gemini/skills/android-maps-utils/SKILL.md @@ -16,7 +16,6 @@ dependencies { // Google Maps Utility Library implementation("com.google.maps.android:android-maps-utils:5.2.0") // x-release-please-version } - ``` ## 2. Core Features & Usage Patterns diff --git a/build.gradle.kts b/build.gradle.kts index 4ca1374c6..feb04cf54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,7 +55,6 @@ allprojects { version = "5.2.0" // {x-release-please-end} - plugins.withId("java") { configure { toolchain { diff --git a/clustering/build.gradle.kts b/clustering/build.gradle.kts index a17bcc2f1..13582eba6 100644 --- a/clustering/build.gradle.kts +++ b/clustering/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation(project(":library")) implementation(project(":data")) api(libs.play.services.maps) + api(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.android) implementation(libs.appcompat) implementation(libs.core.ktx) @@ -78,11 +79,8 @@ dependencies { testImplementation(libs.kotlin.test) testImplementation(libs.truth) implementation(libs.kotlin.stdlib.jdk8) - - testImplementation(libs.mockk) testImplementation(libs.kotlinx.coroutines.test) - testImplementation(libs.robolectric) - testImplementation(libs.mockito.core) + testImplementation(libs.mockito.kotlin) } tasks.register("instrumentTest") { diff --git a/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt index 4952e8e86..b531d6347 100644 --- a/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt @@ -23,8 +23,8 @@ import com.google.maps.android.clustering.Cluster import com.google.maps.android.clustering.ClusterItem import com.google.maps.android.clustering.ClusterManager import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test @@ -68,73 +68,67 @@ public class ClusterManagerTest { @Test public fun testClusterClickEvents(): Unit = runTest { - val job = launch { - val event = clusterManager.clusterClickEvents().first() - assertThat(event).isEqualTo(cluster) + val deferred = async { + clusterManager.clusterClickEvents().first() } advanceUntilIdle() verify(clusterManager).setOnClusterClickListener(clusterClickListener.capture()) clusterClickListener.value.onClusterClick(cluster) - job.cancel() + assertThat(deferred.await()).isEqualTo(cluster) } @Test public fun testClusterItemClickEvents(): Unit = runTest { - val job = launch { - val event = clusterManager.clusterItemClickEvents().first() - assertThat(event).isEqualTo(clusterItem) + val deferred = async { + clusterManager.clusterItemClickEvents().first() } advanceUntilIdle() verify(clusterManager).setOnClusterItemClickListener(clusterItemClickListener.capture()) clusterItemClickListener.value.onClusterItemClick(clusterItem) - job.cancel() + assertThat(deferred.await()).isEqualTo(clusterItem) } @Test public fun testClusterInfoWindowClickEvents(): Unit = runTest { - val job = launch { - val event = clusterManager.clusterInfoWindowClickEvents().first() - assertThat(event).isEqualTo(cluster) + val deferred = async { + clusterManager.clusterInfoWindowClickEvents().first() } advanceUntilIdle() verify(clusterManager).setOnClusterInfoWindowClickListener(clusterInfoWindowClickListener.capture()) clusterInfoWindowClickListener.value.onClusterInfoWindowClick(cluster) - job.cancel() + assertThat(deferred.await()).isEqualTo(cluster) } @Test public fun testClusterInfoWindowLongClickEvents(): Unit = runTest { - val job = launch { - val event = clusterManager.clusterInfoWindowLongClickEvents().first() - assertThat(event).isEqualTo(cluster) + val deferred = async { + clusterManager.clusterInfoWindowLongClickEvents().first() } advanceUntilIdle() verify(clusterManager).setOnClusterInfoWindowLongClickListener(clusterInfoWindowLongClickListener.capture()) clusterInfoWindowLongClickListener.value.onClusterInfoWindowLongClick(cluster) - job.cancel() + assertThat(deferred.await()).isEqualTo(cluster) } @Test public fun testClusterItemInfoWindowClickEvents(): Unit = runTest { - val job = launch { - val event = clusterManager.clusterItemInfoWindowClickEvents().first() - assertThat(event).isEqualTo(clusterItem) + val deferred = async { + clusterManager.clusterItemInfoWindowClickEvents().first() } advanceUntilIdle() verify(clusterManager).setOnClusterItemInfoWindowClickListener(clusterItemInfoWindowClickListener.capture()) clusterItemInfoWindowClickListener.value.onClusterItemInfoWindowClick(clusterItem) - job.cancel() + assertThat(deferred.await()).isEqualTo(clusterItem) } @Test public fun testClusterItemInfoWindowLongClickEvents(): Unit = runTest { - val job = launch { - val event = clusterManager.clusterItemInfoWindowLongClickEvents().first() - assertThat(event).isEqualTo(clusterItem) + val deferred = async { + clusterManager.clusterItemInfoWindowLongClickEvents().first() } advanceUntilIdle() verify(clusterManager).setOnClusterItemInfoWindowLongClickListener(clusterItemInfoWindowLongClickListener.capture()) clusterItemInfoWindowLongClickListener.value.onClusterItemInfoWindowLongClick(clusterItem) - job.cancel() + assertThat(deferred.await()).isEqualTo(clusterItem) } } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 7445fcc7e..d81754ca4 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -79,11 +79,8 @@ dependencies { testImplementation(libs.truth) testImplementation(libs.androidx.test.core) implementation(libs.kotlin.stdlib.jdk8) - - testImplementation(libs.mockk) testImplementation(libs.kotlinx.coroutines.test) - testImplementation(libs.robolectric) - testImplementation(libs.mockito.core) + testImplementation(libs.mockito.kotlin) } tasks.register("instrumentTest") { diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt index 4044d3515..bef287ed0 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap @@ -46,7 +45,6 @@ import com.google.maps.android.awaitMap import com.google.maps.android.awaitMapsSdkInitialized import com.google.maps.android.ktx.addCircle as deprecatedBridgeAddCircle import com.google.maps.android.mapClickEvents -import kotlinx.coroutines.launch /** * A demo activity illustrating the consolidated reactive Coroutine/Flow/Builder extensions @@ -89,7 +87,7 @@ class KtxExtensionsDemoActivity : ComponentActivity() { } } - LaunchedEffect(mapView) { + LaunchedEffect(lifecycleOwner, mapView) { // 1. Canonical awaitMapsSdkInitialized() coroutine suspension context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) @@ -97,7 +95,7 @@ class KtxExtensionsDemoActivity : ComponentActivity() { val googleMap: GoogleMap = mapView.awaitMap() val sydney = LatLng(-33.852, 151.211) - + // 3. Canonical addMarker builder DSL googleMap.addMarker { position(sydney) @@ -114,16 +112,14 @@ class KtxExtensionsDemoActivity : ComponentActivity() { // 4. Canonical awaitAnimateCamera suspension googleMap.awaitAnimateCamera(CameraUpdateFactory.newLatLngZoom(sydney, 12f), 1500) - // 5. Canonical Flow observation for map clicks - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - googleMap.mapClickEvents().collect { latLng -> - Toast.makeText( - context, - "Clicked at: ${latLng.latitude}, ${latLng.longitude}", - Toast.LENGTH_SHORT - ).show() - } + // 5. Canonical Flow observation for map clicks tied to the composable & lifecycle + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + googleMap.mapClickEvents().collect { latLng -> + Toast.makeText( + context, + "Clicked at: ${latLng.latitude}, ${latLng.longitude}", + Toast.LENGTH_SHORT + ).show() } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1ba2cdaf6..bd639fb10 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,8 +43,7 @@ androidx-test-ext-junit = "1.3.0" espresso-core = "3.7.0" junit = "4.13.2" kxml2 = "2.3.0" -mockito-core = "5.23.0" -mockito-kotlin = "2.2.0" +mockito-kotlin = "5.4.0" mockk = "1.14.11" robolectric = "4.17" truth = "1.4.5" @@ -110,8 +109,7 @@ androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-te androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } junit = { module = "junit:junit", version.ref = "junit" } -mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-core" } -mockito-kotlin = { module = "com.nhaarman.mockitokotlin2:mockito-kotlin", version.ref = "mockito-kotlin" } +mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-kotlin" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } truth = { module = "com.google.truth:truth", version.ref = "truth" } diff --git a/heatmaps/build.gradle.kts b/heatmaps/build.gradle.kts index 08b2f81e1..357967b0e 100644 --- a/heatmaps/build.gradle.kts +++ b/heatmaps/build.gradle.kts @@ -77,11 +77,8 @@ dependencies { testImplementation(libs.kotlin.test) testImplementation(libs.truth) implementation(libs.kotlin.stdlib.jdk8) - - testImplementation(libs.mockk) testImplementation(libs.kotlinx.coroutines.test) - testImplementation(libs.robolectric) - testImplementation(libs.mockito.core) + testImplementation(libs.mockito.kotlin) } tasks.register("instrumentTest") { diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 2b4864d2a..8eca4621f 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -63,13 +63,14 @@ android { dependencies { api(libs.play.services.maps) - api(libs.play.services.location) - api(libs.kotlinx.coroutines.android) + compileOnly(libs.play.services.location) api(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.coroutines.android) implementation(libs.appcompat) implementation(libs.core.ktx) implementation(libs.startup.runtime) lintPublish(project(":lint-checks")) + testImplementation(libs.play.services.location) testImplementation(libs.junit) testImplementation(libs.robolectric) testImplementation(libs.kxml2) @@ -78,11 +79,7 @@ dependencies { testImplementation(libs.androidx.test.core) testImplementation(libs.truth) implementation(libs.kotlin.stdlib.jdk8) - - testImplementation(libs.mockk) testImplementation(libs.kotlinx.coroutines.test) - testImplementation(libs.robolectric) - testImplementation(libs.mockito.core) testImplementation(libs.mockito.kotlin) } @@ -142,7 +139,3 @@ androidComponents { tasks.named("dokkaGeneratePublicationHtml") { dependsOn(generateArtifactIdFile) } - -tasks.named("dokkaGeneratePublicationHtml") { - dependsOn(generateArtifactIdFile) -} diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro index e69de29bb..d700d75cf 100644 --- a/library/consumer-rules.pro +++ b/library/consumer-rules.pro @@ -0,0 +1,3 @@ +# play-services-location is an optional compileOnly dependency for FusedLocationProviderClient extensions. +# Suppress R8 missing-class warnings for consumers that do not include play-services-location. +-dontwarn com.google.android.gms.location.** diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index dd18477c1..d11680548 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -28,9 +28,6 @@ - diff --git a/library/src/main/java/com/google/maps/android/GoogleMap.kt b/library/src/main/java/com/google/maps/android/GoogleMap.kt index 2345e8f68..3491d0bad 100644 --- a/library/src/main/java/com/google/maps/android/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/GoogleMap.kt @@ -167,14 +167,14 @@ public fun GoogleMap.cameraEvents(): Flow = * A suspending function that awaits the completion of the [cameraUpdate] animation. * * @param cameraUpdate the [CameraUpdate] to apply on the map - * @param durationMs the duration in milliseconds of the animation. Defaults to 3 seconds. + * @param durationMs the duration in milliseconds of the animation, or `null` to use the Maps SDK default duration */ public suspend fun GoogleMap.awaitAnimateCamera( cameraUpdate: CameraUpdate, - durationMs: Int = 3000 + durationMs: Int? = null ): Unit = suspendCancellableCoroutine { continuation -> - animateCamera(cameraUpdate, durationMs, object : GoogleMap.CancelableCallback { + val callback = object : GoogleMap.CancelableCallback { override fun onFinish() { if (continuation.isActive) { continuation.resume(Unit) @@ -186,7 +186,12 @@ public suspend fun GoogleMap.awaitAnimateCamera( continuation.cancel() } } - }) + } + if (durationMs != null) { + animateCamera(cameraUpdate, durationMs, callback) + } else { + animateCamera(cameraUpdate, callback) + } } /** diff --git a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt index 5cabc2478..dcb55f5f9 100644 --- a/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt +++ b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.kt @@ -125,7 +125,7 @@ public typealias IndoorLevelActivatedEvent = CanonicalIndoorLevelActivatedEvent public fun GoogleMap.cameraEvents(): Flow = this.canonicalCameraEvents() @Deprecated("Moved to com.google.maps.android.awaitAnimateCamera", ReplaceWith("awaitAnimateCamera(cameraUpdate, durationMs)", "com.google.maps.android.awaitAnimateCamera")) -public suspend fun GoogleMap.awaitAnimateCamera(cameraUpdate: CameraUpdate, durationMs: Int = 3000): Unit = this.canonicalAwaitAnimateCamera(cameraUpdate, durationMs) +public suspend fun GoogleMap.awaitAnimateCamera(cameraUpdate: CameraUpdate, durationMs: Int? = null): Unit = this.canonicalAwaitAnimateCamera(cameraUpdate, durationMs) @Deprecated("Moved to com.google.maps.android.awaitMapLoad", ReplaceWith("awaitMapLoad()", "com.google.maps.android.awaitMapLoad")) public suspend fun GoogleMap.awaitMapLoad(): Unit = this.canonicalAwaitMapLoad() diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt b/library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt deleted file mode 100644 index be393f6cd..000000000 --- a/library/src/main/java/com/google/maps/android/ktx/utils/attribution/AttributionIdInitializer.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2026 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.maps.android.ktx.utils.attribution - -import android.content.Context -import androidx.annotation.Keep -import androidx.startup.Initializer -import com.google.android.gms.maps.MapsApiSettings -import com.google.maps.android.utils.meta.AttributionId - -@Keep -@Deprecated( - message = "Use com.google.maps.android.utils.attribution.AttributionIdInitializer instead", - replaceWith = ReplaceWith("AttributionIdInitializer", "com.google.maps.android.utils.attribution.AttributionIdInitializer"), - level = DeprecationLevel.WARNING -) -internal class AttributionIdInitializer : Initializer { - override fun create(context: Context) { - MapsApiSettings.addInternalUsageAttributionId( - context, - AttributionId.VALUE - ) - } - - override fun dependencies(): List>> = emptyList() -} diff --git a/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt b/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt index d29f59034..ad7b575c0 100644 --- a/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt +++ b/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.kt @@ -18,21 +18,24 @@ package com.google.maps.android.ktx.utils.location import android.Manifest import android.location.Location import android.location.LocationManager +import android.os.Looper import androidx.annotation.RequiresPermission import kotlinx.coroutines.flow.Flow import com.google.maps.android.location.coarseLocationEvents as canonicalCoarseLocationEvents import com.google.maps.android.location.fineLocationEvents as canonicalFineLocationEvents @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) -@Deprecated("Moved to com.google.maps.android.location.coarseLocationEvents", ReplaceWith("coarseLocationEvents(minTimeMs, minDistanceM)", "com.google.maps.android.location.coarseLocationEvents")) +@Deprecated("Moved to com.google.maps.android.location.coarseLocationEvents", ReplaceWith("coarseLocationEvents(minTimeMs, minDistanceM, looper)", "com.google.maps.android.location.coarseLocationEvents")) public fun LocationManager.coarseLocationEvents( - minTimeMs: Long = 1000L, - minDistanceM: Float = 1f -): Flow = this.canonicalCoarseLocationEvents(minTimeMs, minDistanceM) + minTimeMs: Long = 1_000L, + minDistanceM: Float = 1f, + looper: Looper = Looper.getMainLooper() +): Flow = this.canonicalCoarseLocationEvents(minTimeMs, minDistanceM, looper) @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) -@Deprecated("Moved to com.google.maps.android.location.fineLocationEvents", ReplaceWith("fineLocationEvents(minTimeMs, minDistanceM)", "com.google.maps.android.location.fineLocationEvents")) +@Deprecated("Moved to com.google.maps.android.location.fineLocationEvents", ReplaceWith("fineLocationEvents(minTimeMs, minDistanceM, looper)", "com.google.maps.android.location.fineLocationEvents")) public fun LocationManager.fineLocationEvents( - minTimeMs: Long = 1000L, - minDistanceM: Float = 1f -): Flow = this.canonicalFineLocationEvents(minTimeMs, minDistanceM) + minTimeMs: Long = 1_000L, + minDistanceM: Float = 1f, + looper: Looper = Looper.getMainLooper() +): Flow = this.canonicalFineLocationEvents(minTimeMs, minDistanceM, looper) diff --git a/library/src/main/java/com/google/maps/android/location/LocationManager.kt b/library/src/main/java/com/google/maps/android/location/LocationManager.kt index 213a2a5fd..c834da7ea 100644 --- a/library/src/main/java/com/google/maps/android/location/LocationManager.kt +++ b/library/src/main/java/com/google/maps/android/location/LocationManager.kt @@ -22,8 +22,8 @@ import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.os.Looper import androidx.annotation.RequiresPermission -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -33,7 +33,8 @@ import kotlinx.coroutines.flow.callbackFlow * (or [LocationManager.PASSIVE_PROVIDER] if network provider is not available). * * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately - * when the collector cancels or closes the subscription. + * when the collector cancels or closes the subscription. When the underlying location provider is disabled, + * the flow completes normally. * * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing * multiple collectors will result in listener hijacking, and cancelling any observer will unregister @@ -43,7 +44,8 @@ import kotlinx.coroutines.flow.callbackFlow @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) public fun LocationManager.coarseLocationEvents( minTimeMs: Long = 1_000L, - minDistanceM: Float = 1f + minDistanceM: Float = 1f, + looper: Looper = Looper.getMainLooper() ): Flow = callbackFlow { val listener = object : LocationListener { @@ -61,7 +63,7 @@ public fun LocationManager.coarseLocationEvents( } override fun onProviderDisabled(provider: String) { - close(CancellationException("Location provider $provider was disabled")) + close() } } @@ -71,7 +73,7 @@ public fun LocationManager.coarseLocationEvents( LocationManager.PASSIVE_PROVIDER } - requestLocationUpdates(provider, minTimeMs, minDistanceM, listener) + requestLocationUpdates(provider, minTimeMs, minDistanceM, listener, looper) awaitClose { removeUpdates(listener) @@ -82,7 +84,8 @@ public fun LocationManager.coarseLocationEvents( * Returns a cold flow that emits the device's fine location updates using [LocationManager.GPS_PROVIDER]. * * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately - * when the collector cancels or closes the subscription. + * when the collector cancels or closes the subscription. When the underlying location provider is disabled, + * the flow completes normally. * * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing * multiple collectors will result in listener hijacking, and cancelling any observer will unregister @@ -92,7 +95,8 @@ public fun LocationManager.coarseLocationEvents( @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) public fun LocationManager.fineLocationEvents( minTimeMs: Long = 1_000L, - minDistanceM: Float = 1f + minDistanceM: Float = 1f, + looper: Looper = Looper.getMainLooper() ): Flow = callbackFlow { val listener = object : LocationListener { @@ -110,11 +114,11 @@ public fun LocationManager.fineLocationEvents( } override fun onProviderDisabled(provider: String) { - close(CancellationException("Location provider $provider was disabled")) + close() } } - requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, listener) + requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, listener, looper) awaitClose { removeUpdates(listener) diff --git a/library/src/test/java/com/google/maps/android/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt index 862ab8a46..2c5762a6c 100644 --- a/library/src/test/java/com/google/maps/android/GoogleMapTest.kt +++ b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt @@ -31,8 +31,8 @@ import com.google.android.gms.maps.model.Polyline import com.google.android.gms.maps.model.TileOverlay import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before @@ -60,7 +60,7 @@ import org.mockito.junit.MockitoJUnitRunner * construct valid option objects and delegate to the underlying [GoogleMap] add-overlay methods. * * **How it works:** - * - For **Flow tests**: Uses `runTest` and `launch` to collect the first emitted item (`flow.first()`). + * - For **Flow tests**: Uses `runTest` and `async` to collect the first emitted item (`flow.first()`). * An [ArgumentCaptor] intercepts the SDK listener registered on [GoogleMap]. Invoking the listener's * callback method (`onMapClick`, `onMarkerClick`, etc.) emits the test payload into the flow. * - For **Suspension tests**: Captures the SDK callback (`OnMapLoadedCallback`, `CancelableCallback`, @@ -72,7 +72,7 @@ import org.mockito.junit.MockitoJUnitRunner * - **Code under test:** Correct if listener callbacks emit the exact mock instance to the Flow/coroutine, * and if DSL builders delegate to the canonical [GoogleMap] overlay methods without mutation loss. * - **Test:** Correct because `advanceUntilIdle()` guarantees deterministic coroutine scheduling before - * and after callback invocation, and `assertThat(event).isEqualTo(expected)` confirms exact emission equality. + * and after callback invocation, and `assertThat(deferred.await()).isEqualTo(expected)` confirms exact emission equality. */ @ExperimentalCoroutinesApi @RunWith(MockitoJUnitRunner::class) @@ -191,39 +191,34 @@ public class GoogleMapTest { /** * **Purpose:** Verifies [GoogleMap.cameraIdleEvents] converts camera idle callbacks into a Flow. * **How it works:** Subscribes to `cameraIdleEvents().first()`, captures `OnCameraIdleListener`, and invokes `onCameraIdle()`. - * **How we know it is correct:** Test succeeds if the flow emits when `onCameraIdle()` is called. + * **How we know it is correct:** Test succeeds if `deferred.await()` completes when `onCameraIdle()` is called. */ @Test public fun testCameraIdleEvents(): Unit = runTest { - var eventReceived = false - val job = launch { + val deferred = async { googleMap.cameraIdleEvents().first() - eventReceived = true } advanceUntilIdle() verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) cameraIdleListener.value.onCameraIdle() - advanceUntilIdle() - assertThat(eventReceived).isTrue() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } /** * **Purpose:** Verifies [GoogleMap.cameraMoveEvents] converts camera move callbacks into a Flow. * **How it works:** Subscribes to `cameraMoveEvents().first()`, captures `OnCameraMoveListener`, and calls `onCameraMove()`. - * **How we know it is correct:** Test succeeds if the flow emits when `onCameraMove()` is invoked. + * **How we know it is correct:** Test succeeds if `deferred.await()` returns `Unit` when `onCameraMove()` is invoked. */ @Test public fun testCameraMoveEvents(): Unit = runTest { - val job = launch { - val event = googleMap.cameraMoveEvents().first() - assertThat(event).isNotNull() + val deferred = async { + googleMap.cameraMoveEvents().first() } advanceUntilIdle() verify(googleMap).setOnCameraMoveListener(cameraMoveListener.capture()) cameraMoveListener.value.onCameraMove() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } /** @@ -233,34 +228,29 @@ public class GoogleMapTest { */ @Test public fun testCameraMoveStartedEvents(): Unit = runTest { - val job = launch { - val reason = googleMap.cameraMoveStartedEvents().first() - assertThat(reason).isEqualTo(GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) + val deferred = async { + googleMap.cameraMoveStartedEvents().first() } advanceUntilIdle() verify(googleMap).setOnCameraMoveStartedListener(cameraMoveStartedListener.capture()) cameraMoveStartedListener.value.onCameraMoveStarted(GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) - job.cancel() + assertThat(deferred.await()).isEqualTo(GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) } /** * **Purpose:** Verifies [GoogleMap.cameraMoveCanceledEvents] emits when camera movement is cancelled. * **How it works:** Subscribes to `cameraMoveCanceledEvents().first()`, captures listener, and calls `onCameraMoveCanceled()`. - * **How we know it is correct:** Asserts the flow emits a non-null Unit event upon cancellation. + * **How we know it is correct:** Asserts the flow emits a Unit event upon cancellation. */ @Test public fun testCameraMoveCanceledEvents(): Unit = runTest { - var eventReceived = false - val job = launch { + val deferred = async { googleMap.cameraMoveCanceledEvents().first() - eventReceived = true } advanceUntilIdle() verify(googleMap).setOnCameraMoveCanceledListener(cameraMoveCanceledListener.capture()) cameraMoveCanceledListener.value.onCameraMoveCanceled() - advanceUntilIdle() - assertThat(eventReceived).isTrue() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } @@ -272,14 +262,13 @@ public class GoogleMapTest { @Test public fun testMapClickEvents(): Unit = runTest { val target = LatLng(10.0, 20.0) - val job = launch { - val event = googleMap.mapClickEvents().first() - assertThat(event).isEqualTo(target) + val deferred = async { + googleMap.mapClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnMapClickListener(mapClickListener.capture()) mapClickListener.value.onMapClick(target) - job.cancel() + assertThat(deferred.await()).isEqualTo(target) } /** @@ -290,14 +279,13 @@ public class GoogleMapTest { @Test public fun testMapLongClickEvents(): Unit = runTest { val target = LatLng(30.0, 40.0) - val job = launch { - val event = googleMap.mapLongClickEvents().first() - assertThat(event).isEqualTo(target) + val deferred = async { + googleMap.mapLongClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnMapLongClickListener(mapLongClickListener.capture()) mapLongClickListener.value.onMapLongClick(target) - job.cancel() + assertThat(deferred.await()).isEqualTo(target) } /** @@ -307,14 +295,13 @@ public class GoogleMapTest { */ @Test public fun testMarkerClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.markerClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + googleMap.markerClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnMarkerClickListener(markerClickListener.capture()) markerClickListener.value.onMarkerClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } /** @@ -324,14 +311,13 @@ public class GoogleMapTest { */ @Test public fun testMarkerDragEvents(): Unit = runTest { - val job = launch { - val event = googleMap.markerDragEvents().first() - assertThat(event.marker).isEqualTo(marker) + val deferred = async { + googleMap.markerDragEvents().first() } advanceUntilIdle() verify(googleMap).setOnMarkerDragListener(markerDragListener.capture()) markerDragListener.value.onMarkerDragStart(marker) - job.cancel() + assertThat(deferred.await().marker).isEqualTo(marker) } /** @@ -341,14 +327,13 @@ public class GoogleMapTest { */ @Test public fun testInfoWindowClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.infoWindowClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + googleMap.infoWindowClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnInfoWindowClickListener(infoWindowClickListener.capture()) infoWindowClickListener.value.onInfoWindowClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } /** @@ -358,14 +343,13 @@ public class GoogleMapTest { */ @Test public fun testInfoWindowCloseEvents(): Unit = runTest { - val job = launch { - val event = googleMap.infoWindowCloseEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + googleMap.infoWindowCloseEvents().first() } advanceUntilIdle() verify(googleMap).setOnInfoWindowCloseListener(infoWindowCloseListener.capture()) infoWindowCloseListener.value.onInfoWindowClose(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } /** @@ -375,14 +359,13 @@ public class GoogleMapTest { */ @Test public fun testInfoWindowLongClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.infoWindowLongClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + googleMap.infoWindowLongClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) infoWindowLongClickListener.value.onInfoWindowLongClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } /** @@ -392,14 +375,13 @@ public class GoogleMapTest { */ @Test public fun testPolygonClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.polygonClickEvents().first() - assertThat(event).isEqualTo(polygon) + val deferred = async { + googleMap.polygonClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnPolygonClickListener(polygonClickListener.capture()) polygonClickListener.value.onPolygonClick(polygon) - job.cancel() + assertThat(deferred.await()).isEqualTo(polygon) } /** @@ -409,14 +391,13 @@ public class GoogleMapTest { */ @Test public fun testPolylineClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.polylineClickEvents().first() - assertThat(event).isEqualTo(polyline) + val deferred = async { + googleMap.polylineClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnPolylineClickListener(polylineClickListener.capture()) polylineClickListener.value.onPolylineClick(polyline) - job.cancel() + assertThat(deferred.await()).isEqualTo(polyline) } /** @@ -426,14 +407,13 @@ public class GoogleMapTest { */ @Test public fun testCircleClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.circleClickEvents().first() - assertThat(event).isEqualTo(circle) + val deferred = async { + googleMap.circleClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnCircleClickListener(circleClickListener.capture()) circleClickListener.value.onCircleClick(circle) - job.cancel() + assertThat(deferred.await()).isEqualTo(circle) } /** @@ -443,14 +423,13 @@ public class GoogleMapTest { */ @Test public fun testGroundOverlayClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.groundOverlayClicks().first() - assertThat(event).isEqualTo(groundOverlay) + val deferred = async { + googleMap.groundOverlayClicks().first() } advanceUntilIdle() verify(googleMap).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) - job.cancel() + assertThat(deferred.await()).isEqualTo(groundOverlay) } /** @@ -461,14 +440,13 @@ public class GoogleMapTest { @Test public fun testPoiClickEvents(): Unit = runTest { val poi = PointOfInterest(LatLng(1.0, 2.0), "id", "name") - val job = launch { - val event = googleMap.poiClickEvents().first() - assertThat(event).isEqualTo(poi) + val deferred = async { + googleMap.poiClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnPoiClickListener(poiClickListener.capture()) poiClickListener.value.onPoiClick(poi) - job.cancel() + assertThat(deferred.await()).isEqualTo(poi) } /** @@ -478,34 +456,29 @@ public class GoogleMapTest { */ @Test public fun testMyLocationClickEvents(): Unit = runTest { - val job = launch { - val event = googleMap.myLocationClickEvents().first() - assertThat(event).isEqualTo(location) + val deferred = async { + googleMap.myLocationClickEvents().first() } advanceUntilIdle() verify(googleMap).setOnMyLocationClickListener(myLocationClickListener.capture()) myLocationClickListener.value.onMyLocationClick(location) - job.cancel() + assertThat(deferred.await()).isEqualTo(location) } /** * **Purpose:** Verifies [GoogleMap.myLocationButtonClickEvents] emits when the My Location button is clicked. * **How it works:** Subscribes to `myLocationButtonClickEvents().first()`, captures listener, and calls `onMyLocationButtonClick()`. - * **How we know it is correct:** Asserts the emitted Unit event is non-null. + * **How we know it is correct:** Asserts the emitted Unit event is received via `deferred.await()`. */ @Test public fun testMyLocationButtonClickEvents(): Unit = runTest { - var eventReceived = false - val job = launch { + val deferred = async { googleMap.myLocationButtonClickEvents().first() - eventReceived = true } advanceUntilIdle() verify(googleMap).setOnMyLocationButtonClickListener(myLocationButtonClickListener.capture()) myLocationButtonClickListener.value.onMyLocationButtonClick() - advanceUntilIdle() - assertThat(eventReceived).isTrue() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } /** @@ -515,14 +488,13 @@ public class GoogleMapTest { */ @Test public fun testIndoorStateChangeEvents(): Unit = runTest { - val job = launch { - val event = googleMap.indoorStateChangeEvents().first() - assertThat(event).isNotNull() + val deferred = async { + googleMap.indoorStateChangeEvents().first() } advanceUntilIdle() verify(googleMap).setOnIndoorStateChangeListener(indoorStateChangeListener.capture()) indoorStateChangeListener.value.onIndoorBuildingFocused() - job.cancel() + assertThat(deferred.await()).isNotNull() } // --------------------------------------------------------------------------------------------- @@ -536,37 +508,37 @@ public class GoogleMapTest { */ @Test public fun testAwaitMapLoad(): Unit = runTest { - var mapLoaded = false - val job = launch { + val deferred = async { googleMap.awaitMapLoad() - mapLoaded = true } advanceUntilIdle() verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) loadedCallback.value.onMapLoaded() - advanceUntilIdle() - assertThat(mapLoaded).isTrue() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } /** - * **Purpose:** Verifies [GoogleMap.awaitAnimateCamera] suspends until camera animation finishes. - * **How it works:** Launches `awaitAnimateCamera(cameraUpdate)`, captures `CancelableCallback` from `animateCamera(update, 3000, callback)`, and calls `onFinish()`. - * **How we know it is correct:** Asserts `animateCamera` was called with default 3000ms duration and coroutine resumes upon `onFinish()`. + * **Purpose:** Verifies [GoogleMap.awaitAnimateCamera] suspends until camera animation finishes, delegating to 2-arg `animateCamera` when `durationMs` is omitted (`null`) and 3-arg `animateCamera` when `durationMs` is specified. + * **How it works:** Launches `awaitAnimateCamera(cameraUpdate)` and `awaitAnimateCamera(cameraUpdate, 500)`, captures `CancelableCallback`, and calls `onFinish()`. + * **How we know it is correct:** Asserts `animateCamera` was called with the appropriate 2-arg or 3-arg overload and coroutine resumes upon `onFinish()`. */ @Test public fun testAwaitAnimateCamera(): Unit = runTest { - var animated = false - val job = launch { + val deferredDefault = async { googleMap.awaitAnimateCamera(cameraUpdate) - animated = true } advanceUntilIdle() - verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(3000), cancelableCallback.capture()) + verify(googleMap).animateCamera(any(CameraUpdate::class.java), cancelableCallback.capture()) cancelableCallback.value.onFinish() + assertThat(deferredDefault.await()).isEqualTo(Unit) + + val deferredWithDuration = async { + googleMap.awaitAnimateCamera(cameraUpdate, 500) + } advanceUntilIdle() - assertThat(animated).isTrue() - job.cancel() + verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(500), cancelableCallback.capture()) + cancelableCallback.value.onFinish() + assertThat(deferredWithDuration.await()).isEqualTo(Unit) } @@ -581,13 +553,10 @@ public class GoogleMapTest { val cb = it.getArgument(0) cb.onSnapshotReady(bitmap) } - var result: Bitmap? = null - val job = launch { - result = googleMap.awaitSnapshot(bitmap) + val deferred = async { + googleMap.awaitSnapshot(bitmap) } - advanceUntilIdle() - assertThat(result).isEqualTo(bitmap) - job.cancel() + assertThat(deferred.await()).isEqualTo(bitmap) } // --------------------------------------------------------------------------------------------- diff --git a/library/src/test/java/com/google/maps/android/PolygonTest.kt b/library/src/test/java/com/google/maps/android/PolygonTest.kt index 74f217af7..312685436 100644 --- a/library/src/test/java/com/google/maps/android/PolygonTest.kt +++ b/library/src/test/java/com/google/maps/android/PolygonTest.kt @@ -20,8 +20,8 @@ package com.google.maps.android import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Polygon import com.google.common.truth.Truth.assertThat -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.junit.Test internal class PolygonTest { diff --git a/library/src/test/java/com/google/maps/android/PolylineTest.kt b/library/src/test/java/com/google/maps/android/PolylineTest.kt index 8b2ff32d2..26304ab07 100644 --- a/library/src/test/java/com/google/maps/android/PolylineTest.kt +++ b/library/src/test/java/com/google/maps/android/PolylineTest.kt @@ -20,8 +20,8 @@ package com.google.maps.android import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Polyline import com.google.common.truth.Truth.assertThat -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.junit.Test internal class PolylineTest { diff --git a/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt index 634862206..9118b9d33 100644 --- a/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt +++ b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt @@ -34,6 +34,7 @@ import com.google.maps.android.collections.MarkerManager import com.google.maps.android.collections.PolygonManager import com.google.maps.android.collections.PolylineManager import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle @@ -46,7 +47,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.verify -import org.mockito.Mockito.times +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner @OptIn(ExperimentalCoroutinesApi::class) @@ -110,7 +111,7 @@ public class CollectionManagersTest { public fun setUp() { activeMarkerClickListener = null // Stub setOnMarkerClickListener to track the active listener on the collection manager - org.mockito.Mockito.`when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> + `when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> activeMarkerClickListener = invocation.arguments[0] as? GoogleMap.OnMarkerClickListener null } @@ -118,15 +119,14 @@ public class CollectionManagersTest { @Test public fun testMarkerCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = markerCollection.clickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + markerCollection.clickEvents().first() } advanceUntilIdle() // Trigger the event via our tracked active listener slot! assertThat(activeMarkerClickListener).isNotNull() activeMarkerClickListener?.onMarkerClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } @Test @@ -181,73 +181,67 @@ public class CollectionManagersTest { @Test public fun testMarkerCollectionInfoWindowClickEvents(): Unit = runTest { - val job = launch { - val event = markerCollection.infoWindowClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + markerCollection.infoWindowClickEvents().first() } advanceUntilIdle() verify(markerCollection).setOnInfoWindowClickListener(infoWindowClickListener.capture()) infoWindowClickListener.value.onInfoWindowClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } @Test public fun testMarkerCollectionInfoWindowLongClickEvents(): Unit = runTest { - val job = launch { - val event = markerCollection.infoWindowLongClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + markerCollection.infoWindowLongClickEvents().first() } advanceUntilIdle() verify(markerCollection).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) infoWindowLongClickListener.value.onInfoWindowLongClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } @Test public fun testPolylineCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = polylineCollection.clickEvents().first() - assertThat(event).isEqualTo(polyline) + val deferred = async { + polylineCollection.clickEvents().first() } advanceUntilIdle() verify(polylineCollection).setOnPolylineClickListener(polylineClickListener.capture()) polylineClickListener.value.onPolylineClick(polyline) - job.cancel() + assertThat(deferred.await()).isEqualTo(polyline) } @Test public fun testPolygonCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = polygonCollection.clickEvents().first() - assertThat(event).isEqualTo(polygon) + val deferred = async { + polygonCollection.clickEvents().first() } advanceUntilIdle() verify(polygonCollection).setOnPolygonClickListener(polygonClickListener.capture()) polygonClickListener.value.onPolygonClick(polygon) - job.cancel() + assertThat(deferred.await()).isEqualTo(polygon) } @Test public fun testCircleCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = circleCollection.clickEvents().first() - assertThat(event).isEqualTo(circle) + val deferred = async { + circleCollection.clickEvents().first() } advanceUntilIdle() verify(circleCollection).setOnCircleClickListener(circleClickListener.capture()) circleClickListener.value.onCircleClick(circle) - job.cancel() + assertThat(deferred.await()).isEqualTo(circle) } @Test public fun testGroundOverlayCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = groundOverlayCollection.clickEvents().first() - assertThat(event).isEqualTo(groundOverlay) + val deferred = async { + groundOverlayCollection.clickEvents().first() } advanceUntilIdle() verify(groundOverlayCollection).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) - job.cancel() + assertThat(deferred.await()).isEqualTo(groundOverlay) } } diff --git a/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt index cc33b40e4..dd8b42f29 100644 --- a/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt @@ -21,8 +21,8 @@ import com.google.android.gms.maps.CameraUpdate import com.google.android.gms.maps.GoogleMap import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test @@ -56,47 +56,43 @@ public class GoogleMapTest { @Test public fun testCameraIdleEvents(): Unit = runTest { - var received = false - val job = launch { + val deferred = async { googleMap.cameraIdleEvents().first() - received = true } advanceUntilIdle() verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) cameraIdleListener.value.onCameraIdle() - advanceUntilIdle() - assertThat(received).isTrue() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } @Test public fun testAwaitMapLoad(): Unit = runTest { - var mapLoaded = false - val job = launch { + val deferred = async { googleMap.awaitMapLoad() - mapLoaded = true } advanceUntilIdle() verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) loadedCallback.value.onMapLoaded() - advanceUntilIdle() - assertThat(mapLoaded).isTrue() - job.cancel() + assertThat(deferred.await()).isEqualTo(Unit) } @Test public fun testAwaitAnimateCamera(): Unit = runTest { - var animated = false - val job = launch { + val deferredDefault = async { googleMap.awaitAnimateCamera(cameraUpdate) - animated = true } advanceUntilIdle() - verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(3000), cancelableCallback.capture()) + verify(googleMap).animateCamera(any(CameraUpdate::class.java), cancelableCallback.capture()) cancelableCallback.value.onFinish() + assertThat(deferredDefault.await()).isEqualTo(Unit) + + val deferredWithDuration = async { + googleMap.awaitAnimateCamera(cameraUpdate, 500) + } advanceUntilIdle() - assertThat(animated).isTrue() - job.cancel() + verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(500), cancelableCallback.capture()) + cancelableCallback.value.onFinish() + assertThat(deferredWithDuration.await()).isEqualTo(Unit) } } diff --git a/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt index 863feec3c..477f8d60e 100644 --- a/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt @@ -20,7 +20,7 @@ package com.google.maps.android.ktx.model import com.google.android.gms.maps.model.BitmapDescriptor import com.google.common.truth.Truth.assertThat -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.mock import org.junit.Test internal class GroundOverlayOptionsTest { diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt index 28daeefef..f3f0df6e5 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.kt @@ -21,8 +21,8 @@ package com.google.maps.android.ktx.utils import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Polygon import com.google.common.truth.Truth.assertThat -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.junit.Test internal class PolygonTest { diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt index ae09ce1a5..4b32a2b75 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.kt @@ -21,8 +21,8 @@ package com.google.maps.android.ktx.utils import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Polyline import com.google.common.truth.Truth.assertThat -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.junit.Test internal class PolylineTest { diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt index 283f366a8..ebca42b2c 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt @@ -35,6 +35,7 @@ import com.google.maps.android.collections.MarkerManager import com.google.maps.android.collections.PolygonManager import com.google.maps.android.collections.PolylineManager import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle @@ -47,7 +48,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.verify -import org.mockito.Mockito.times +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner @OptIn(ExperimentalCoroutinesApi::class) @@ -111,7 +112,7 @@ public class CollectionManagersTest { public fun setUp() { activeMarkerClickListener = null // Stub setOnMarkerClickListener to track the active listener on the collection manager - org.mockito.Mockito.`when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> + `when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> activeMarkerClickListener = invocation.arguments[0] as? GoogleMap.OnMarkerClickListener null } @@ -119,15 +120,14 @@ public class CollectionManagersTest { @Test public fun testMarkerCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = markerCollection.clickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + markerCollection.clickEvents().first() } advanceUntilIdle() // Trigger the event via our tracked active listener slot! assertThat(activeMarkerClickListener).isNotNull() activeMarkerClickListener?.onMarkerClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } @Test @@ -182,73 +182,67 @@ public class CollectionManagersTest { @Test public fun testMarkerCollectionInfoWindowClickEvents(): Unit = runTest { - val job = launch { - val event = markerCollection.infoWindowClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + markerCollection.infoWindowClickEvents().first() } advanceUntilIdle() verify(markerCollection).setOnInfoWindowClickListener(infoWindowClickListener.capture()) infoWindowClickListener.value.onInfoWindowClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } @Test public fun testMarkerCollectionInfoWindowLongClickEvents(): Unit = runTest { - val job = launch { - val event = markerCollection.infoWindowLongClickEvents().first() - assertThat(event).isEqualTo(marker) + val deferred = async { + markerCollection.infoWindowLongClickEvents().first() } advanceUntilIdle() verify(markerCollection).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) infoWindowLongClickListener.value.onInfoWindowLongClick(marker) - job.cancel() + assertThat(deferred.await()).isEqualTo(marker) } @Test public fun testPolylineCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = polylineCollection.clickEvents().first() - assertThat(event).isEqualTo(polyline) + val deferred = async { + polylineCollection.clickEvents().first() } advanceUntilIdle() verify(polylineCollection).setOnPolylineClickListener(polylineClickListener.capture()) polylineClickListener.value.onPolylineClick(polyline) - job.cancel() + assertThat(deferred.await()).isEqualTo(polyline) } @Test public fun testPolygonCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = polygonCollection.clickEvents().first() - assertThat(event).isEqualTo(polygon) + val deferred = async { + polygonCollection.clickEvents().first() } advanceUntilIdle() verify(polygonCollection).setOnPolygonClickListener(polygonClickListener.capture()) polygonClickListener.value.onPolygonClick(polygon) - job.cancel() + assertThat(deferred.await()).isEqualTo(polygon) } @Test public fun testCircleCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = circleCollection.clickEvents().first() - assertThat(event).isEqualTo(circle) + val deferred = async { + circleCollection.clickEvents().first() } advanceUntilIdle() verify(circleCollection).setOnCircleClickListener(circleClickListener.capture()) circleClickListener.value.onCircleClick(circle) - job.cancel() + assertThat(deferred.await()).isEqualTo(circle) } @Test public fun testGroundOverlayCollectionClickEvents(): Unit = runTest { - val job = launch { - val event = groundOverlayCollection.clickEvents().first() - assertThat(event).isEqualTo(groundOverlay) + val deferred = async { + groundOverlayCollection.clickEvents().first() } advanceUntilIdle() verify(groundOverlayCollection).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) - job.cancel() + assertThat(deferred.await()).isEqualTo(groundOverlay) } } diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt index e3e3d361d..0bba99dea 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt @@ -22,16 +22,18 @@ import android.annotation.SuppressLint import android.location.Location import android.location.LocationListener import android.location.LocationManager +import android.os.Looper import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.eq @@ -51,37 +53,33 @@ public class LocationManagerTest { @Mock private lateinit var location: Location + @Mock + private lateinit var looper: Looper + @Captor private lateinit var locationListenerCaptor: ArgumentCaptor @SuppressLint("MissingPermission") @Test public fun testCoarseLocationEvents(): Unit = runTest { - // Setup providers mock `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) - val job = launch { - val event = locationManager.coarseLocationEvents(1000L, 1f).first() - assertThat(event).isEqualTo(location) + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).first() } advanceUntilIdle() - // Verify registered with correct provider and capture listener verify(locationManager).requestLocationUpdates( eq(LocationManager.NETWORK_PROVIDER), - eq(1000L), + eq(1_000L), eq(1f), - locationListenerCaptor.capture() + locationListenerCaptor.capture(), + eq(looper) ) - // Trigger event locationListenerCaptor.value.onLocationChanged(location) - advanceUntilIdle() - - job.cancel() - advanceUntilIdle() + assertThat(deferred.await()).isEqualTo(location) - // Verify cleanup verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } @@ -90,13 +88,8 @@ public class LocationManagerTest { public fun testCoarseLocationProviderDisabled(): Unit = runTest { `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) - val exceptions = mutableListOf() - val job = launch { - try { - locationManager.coarseLocationEvents(1000L, 1f).collect {} - } catch (e: Throwable) { - exceptions.add(e) - } + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).toList() } advanceUntilIdle() @@ -104,48 +97,36 @@ public class LocationManagerTest { eq(LocationManager.NETWORK_PROVIDER), anyLong(), anyFloat(), - locationListenerCaptor.capture() + locationListenerCaptor.capture(), + any() ) - // Simulate provider disablement! locationListenerCaptor.value.onProviderDisabled(LocationManager.NETWORK_PROVIDER) advanceUntilIdle() - // Verify that the flow threw a CancellationException and terminated cleanly - assertThat(exceptions).hasSize(1) - assertThat(exceptions.first()).isInstanceOf(CancellationException::class.java) - assertThat(exceptions.first().message).contains("Location provider ${LocationManager.NETWORK_PROVIDER} was disabled") - - // Verify cleanup runs automatically on closure! + assertThat(deferred.await()).isEmpty() verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) - job.cancel() } @SuppressLint("MissingPermission") @Test public fun testFineLocationEvents(): Unit = runTest { - val job = launch { - val event = locationManager.fineLocationEvents(2000L, 2f).first() - assertThat(event).isEqualTo(location) + val deferred = async { + locationManager.fineLocationEvents(2_000L, 2f, looper).first() } advanceUntilIdle() - // Verify registered with GPS provider verify(locationManager).requestLocationUpdates( eq(LocationManager.GPS_PROVIDER), - eq(2000L), + eq(2_000L), eq(2f), - locationListenerCaptor.capture() + locationListenerCaptor.capture(), + eq(looper) ) - // Trigger event locationListenerCaptor.value.onLocationChanged(location) - advanceUntilIdle() - - job.cancel() - advanceUntilIdle() + assertThat(deferred.await()).isEqualTo(location) - // Verify cleanup verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } } diff --git a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt index e4d88aa38..bbe9d9738 100644 --- a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt @@ -21,16 +21,18 @@ import android.annotation.SuppressLint import android.location.Location import android.location.LocationListener import android.location.LocationManager +import android.os.Looper import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.eq @@ -50,37 +52,33 @@ public class LocationManagerTest { @Mock private lateinit var location: Location + @Mock + private lateinit var looper: Looper + @Captor private lateinit var locationListenerCaptor: ArgumentCaptor @SuppressLint("MissingPermission") @Test public fun testCoarseLocationEvents(): Unit = runTest { - // Setup providers mock `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) - val job = launch { - val event = locationManager.coarseLocationEvents(1000L, 1f).first() - assertThat(event).isEqualTo(location) + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).first() } advanceUntilIdle() - // Verify registered with correct provider and capture listener verify(locationManager).requestLocationUpdates( eq(LocationManager.NETWORK_PROVIDER), - eq(1000L), + eq(1_000L), eq(1f), - locationListenerCaptor.capture() + locationListenerCaptor.capture(), + eq(looper) ) - // Trigger event locationListenerCaptor.value.onLocationChanged(location) - advanceUntilIdle() + assertThat(deferred.await()).isEqualTo(location) - job.cancel() - advanceUntilIdle() - - // Verify cleanup verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } @@ -89,13 +87,8 @@ public class LocationManagerTest { public fun testCoarseLocationProviderDisabled(): Unit = runTest { `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) - val exceptions = mutableListOf() - val job = launch { - try { - locationManager.coarseLocationEvents(1000L, 1f).collect {} - } catch (e: Throwable) { - exceptions.add(e) - } + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).toList() } advanceUntilIdle() @@ -103,48 +96,37 @@ public class LocationManagerTest { eq(LocationManager.NETWORK_PROVIDER), anyLong(), anyFloat(), - locationListenerCaptor.capture() + locationListenerCaptor.capture(), + any() ) - // Simulate provider disablement! + locationListenerCaptor.value.onLocationChanged(location) locationListenerCaptor.value.onProviderDisabled(LocationManager.NETWORK_PROVIDER) - advanceUntilIdle() - - // Verify that the flow threw a CancellationException and terminated cleanly - assertThat(exceptions).hasSize(1) - assertThat(exceptions.first()).isInstanceOf(CancellationException::class.java) - assertThat(exceptions.first().message).contains("Location provider ${LocationManager.NETWORK_PROVIDER} was disabled") - // Verify cleanup runs automatically on closure! + // Verify that the flow completed normally with the emitted item and cleaned up its listener + assertThat(deferred.await()).containsExactly(location) verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) - job.cancel() } @SuppressLint("MissingPermission") @Test public fun testFineLocationEvents(): Unit = runTest { - val job = launch { - val event = locationManager.fineLocationEvents(2000L, 2f).first() - assertThat(event).isEqualTo(location) + val deferred = async { + locationManager.fineLocationEvents(2_000L, 2f, looper).first() } advanceUntilIdle() - // Verify registered with GPS provider verify(locationManager).requestLocationUpdates( eq(LocationManager.GPS_PROVIDER), - eq(2000L), + eq(2_000L), eq(2f), - locationListenerCaptor.capture() + locationListenerCaptor.capture(), + eq(looper) ) - // Trigger event locationListenerCaptor.value.onLocationChanged(location) - advanceUntilIdle() - - job.cancel() - advanceUntilIdle() + assertThat(deferred.await()).isEqualTo(location) - // Verify cleanup verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } } diff --git a/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt b/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt index 1344212e3..57efe55b4 100644 --- a/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt +++ b/library/src/test/java/com/google/maps/android/model/GroundOverlayOptionsTest.kt @@ -19,7 +19,7 @@ package com.google.maps.android.model import com.google.android.gms.maps.model.BitmapDescriptor import com.google.common.truth.Truth.assertThat -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.mock import org.junit.Test internal class GroundOverlayOptionsTest { diff --git a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt index a8115991a..5d8270e35 100644 --- a/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt +++ b/library/src/test/java/com/google/maps/android/utils/attribution/AttributionIdInitializerTest.kt @@ -18,7 +18,6 @@ package com.google.maps.android.utils.attribution import android.content.Context import com.google.android.gms.maps.MapsApiSettings import com.google.common.truth.Truth.assertThat -import com.google.maps.android.ktx.utils.attribution.AttributionIdInitializer as KtxAttributionIdInitializer import com.google.maps.android.utils.meta.AttributionId import org.junit.After import org.junit.Before @@ -31,8 +30,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner /** - * Unit test suite for [AttributionIdInitializer] and its deprecated KTX compatibility shim - * [KtxAttributionIdInitializer]. + * Unit test suite for [AttributionIdInitializer]. * * **Purpose:** * Verifies that the Jetpack App Startup `Initializer` for library usage attribution correctly @@ -89,27 +87,4 @@ class AttributionIdInitializerTest { MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE) } } - - /** - * **Purpose:** Tests that the deprecated KTX compatibility shim - * [KtxAttributionIdInitializer] continues to register - * usage attribution without breaking existing apps. - * - * **How it works:** Instantiates the KTX shim class and performs the exact same dependency - * and creation verification as the canonical initializer test. - * - * **How we know it is correct:** - * - **Code under test:** Proves backwards-compatible KTX typealias/shim preserves identical startup behavior. - * - **Test:** Fails if the KTX shim fails to invoke [MapsApiSettings.addInternalUsageAttributionId]. - */ - @Suppress("DEPRECATION") - @Test - fun `test ktx AttributionIdInitializer create and dependencies`() { - val initializer = KtxAttributionIdInitializer() - assertThat(initializer.dependencies()).isEmpty() - initializer.create(context) - mapsApiSettingsMock.verify { - MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE) - } - } } diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 9c3cabd59..48de376aa 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -76,10 +76,8 @@ dependencies { testImplementation(libs.truth) implementation(libs.kotlin.stdlib.jdk8) - testImplementation(libs.mockk) testImplementation(libs.kotlinx.coroutines.test) - testImplementation(libs.robolectric) - testImplementation(libs.mockito.core) + testImplementation(libs.mockito.kotlin) } tasks.register("instrumentTest") { From 48de49d4111ba7a6d7fe80de8bd1546d42fa0347 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:23:19 -0600 Subject: [PATCH 14/17] fix: add R8 consumer rules for optional location and Navigation SDK dependencies --- gradle/libs.versions.toml | 6 +++--- library/consumer-rules.pro | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd639fb10..174e6bf62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,19 +45,19 @@ junit = "4.13.2" kxml2 = "2.3.0" mockito-kotlin = "5.4.0" mockk = "1.14.11" -robolectric = "4.17" +robolectric = "4.16.1" truth = "1.4.5" uiautomator = "2.4.0" # --- Lint & Analysis --- # Versions for code quality and static analysis tools. kover = "0.9.9" -lint = "32.4.0" +lint = "32.3.1" # --- Gradle Plugins --- # Versions for Gradle plugins used in the build process. dokka-gradle-plugin = "2.2.0" -gradle = "9.4.0" +gradle = "9.3.1" gradleMavenPublishPlugin = "0.37.0" secrets-gradle-plugin = "2.0.1" serialization = "1.0.2.1" diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro index d700d75cf..6411008cd 100644 --- a/library/consumer-rules.pro +++ b/library/consumer-rules.pro @@ -1,3 +1,6 @@ # play-services-location is an optional compileOnly dependency for FusedLocationProviderClient extensions. # Suppress R8 missing-class warnings for consumers that do not include play-services-location. -dontwarn com.google.android.gms.location.** + +# Navigation SDK consumers exclude play-services-maps, which transitively provides GooglePlayServicesNotAvailableException. +-dontwarn com.google.android.gms.common.GooglePlayServicesNotAvailableException From 7dbc806363643ebd977d495b9c378b9bb592b3a0 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 29 Sep 2026 11:10:44 -0600 Subject: [PATCH 15/17] fix(location): remove PASSIVE_PROVIDER fallback and guard missing providers --- .../maps/android/location/LocationManager.kt | 43 +++++++++++++------ .../ktx/utils/location/LocationManagerTest.kt | 37 ++++++++++++++++ .../android/location/LocationManagerTest.kt | 37 ++++++++++++++++ 3 files changed, 103 insertions(+), 14 deletions(-) diff --git a/library/src/main/java/com/google/maps/android/location/LocationManager.kt b/library/src/main/java/com/google/maps/android/location/LocationManager.kt index c834da7ea..ac8b8b8a2 100644 --- a/library/src/main/java/com/google/maps/android/location/LocationManager.kt +++ b/library/src/main/java/com/google/maps/android/location/LocationManager.kt @@ -29,12 +29,11 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow /** - * Returns a cold flow that emits the device's coarse location updates using [LocationManager.NETWORK_PROVIDER] - * (or [LocationManager.PASSIVE_PROVIDER] if network provider is not available). + * Returns a cold flow that emits the device's coarse location updates using [LocationManager.NETWORK_PROVIDER]. * * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately - * when the collector cancels or closes the subscription. When the underlying location provider is disabled, - * the flow completes normally. + * when the collector cancels or closes the subscription. When the underlying location provider is unavailable + * or disabled, the flow completes normally. * * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing * multiple collectors will result in listener hijacking, and cancelling any observer will unregister @@ -48,6 +47,11 @@ public fun LocationManager.coarseLocationEvents( looper: Looper = Looper.getMainLooper() ): Flow = callbackFlow { + if (!allProviders.contains(LocationManager.NETWORK_PROVIDER)) { + close() + return@callbackFlow + } + val listener = object : LocationListener { override fun onLocationChanged(location: Location) { trySend(location) @@ -67,13 +71,13 @@ public fun LocationManager.coarseLocationEvents( } } - val provider = if (allProviders.contains(LocationManager.NETWORK_PROVIDER)) { - LocationManager.NETWORK_PROVIDER - } else { - LocationManager.PASSIVE_PROVIDER - } - - requestLocationUpdates(provider, minTimeMs, minDistanceM, listener, looper) + requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + minTimeMs, + minDistanceM, + listener, + looper + ) awaitClose { removeUpdates(listener) @@ -84,8 +88,8 @@ public fun LocationManager.coarseLocationEvents( * Returns a cold flow that emits the device's fine location updates using [LocationManager.GPS_PROVIDER]. * * The location updates start streaming ONLY when the flow is collected, and stop streaming immediately - * when the collector cancels or closes the subscription. When the underlying location provider is disabled, - * the flow completes normally. + * when the collector cancels or closes the subscription. When the underlying location provider is unavailable + * or disabled, the flow completes normally. * * **Warning**: This is a cold flow wrapping a single-listener SDK callback. Concurrently subscribing * multiple collectors will result in listener hijacking, and cancelling any observer will unregister @@ -99,6 +103,11 @@ public fun LocationManager.fineLocationEvents( looper: Looper = Looper.getMainLooper() ): Flow = callbackFlow { + if (!allProviders.contains(LocationManager.GPS_PROVIDER)) { + close() + return@callbackFlow + } + val listener = object : LocationListener { override fun onLocationChanged(location: Location) { trySend(location) @@ -118,7 +127,13 @@ public fun LocationManager.fineLocationEvents( } } - requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, listener, looper) + requestLocationUpdates( + LocationManager.GPS_PROVIDER, + minTimeMs, + minDistanceM, + listener, + looper + ) awaitClose { removeUpdates(listener) diff --git a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt index 0bba99dea..94b8375e6 100644 --- a/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt @@ -40,6 +40,7 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner @@ -108,9 +109,28 @@ public class LocationManagerTest { verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationProviderMissing(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.PASSIVE_PROVIDER)) + + val events = locationManager.coarseLocationEvents(1_000L, 1f, looper).toList() + + assertThat(events).isEmpty() + verify(locationManager, never()).requestLocationUpdates( + any(), + anyLong(), + anyFloat(), + any(), + any() + ) + } + @SuppressLint("MissingPermission") @Test public fun testFineLocationEvents(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.GPS_PROVIDER)) + val deferred = async { locationManager.fineLocationEvents(2_000L, 2f, looper).first() } @@ -129,4 +149,21 @@ public class LocationManagerTest { verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } + + @SuppressLint("MissingPermission") + @Test + public fun testFineLocationProviderMissing(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(emptyList()) + + val events = locationManager.fineLocationEvents(2_000L, 2f, looper).toList() + + assertThat(events).isEmpty() + verify(locationManager, never()).requestLocationUpdates( + any(), + anyLong(), + anyFloat(), + any(), + any() + ) + } } diff --git a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt index bbe9d9738..f2dcd8ce2 100644 --- a/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt @@ -39,6 +39,7 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner @@ -108,9 +109,28 @@ public class LocationManagerTest { verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationProviderMissing(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.PASSIVE_PROVIDER)) + + val events = locationManager.coarseLocationEvents(1_000L, 1f, looper).toList() + + assertThat(events).isEmpty() + verify(locationManager, never()).requestLocationUpdates( + any(), + anyLong(), + anyFloat(), + any(), + any() + ) + } + @SuppressLint("MissingPermission") @Test public fun testFineLocationEvents(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.GPS_PROVIDER)) + val deferred = async { locationManager.fineLocationEvents(2_000L, 2f, looper).first() } @@ -129,4 +149,21 @@ public class LocationManagerTest { verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) } + + @SuppressLint("MissingPermission") + @Test + public fun testFineLocationProviderMissing(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(emptyList()) + + val events = locationManager.fineLocationEvents(2_000L, 2f, looper).toList() + + assertThat(events).isEmpty() + verify(locationManager, never()).requestLocationUpdates( + any(), + anyLong(), + anyFloat(), + any(), + any() + ) + } } From 2662ece8e0fb8bc4953e312ec9604a4052d96981 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:24:03 -0600 Subject: [PATCH 16/17] fix: resolve 10 defects uncovered by Android Maps Testing Toolkit (#1795) * fix: resolve 10 defects uncovered by Android Maps Testing Toolkit Fixes 10 production issues across `:data`, `:heatmaps`, `:clustering`, `:library`, and `:ui` uncovered by deterministic JVM and visual testing with Android Maps Testing Toolkit v1.1.0-rc01: 1. `:data` (`KmlLayer`): Cache `KmlGroundOverlay -> ModelFeature` in `mGroundOverlayMap` so `removeLayerFromMap()` removes ground overlays from `MapViewRenderer`'s `IdentityHashMap` instead of leaking them. 2. `:data` (`Style`, `MapViewRenderer`, `GeoJsonLayer`, `KmlLayer`): Propagate `clickable` (defaulting to `true`) and `visible` to `PolylineOptions` and `PolygonOptions` so GeoJSON and KML polygons and polylines dispatch `OnFeatureClickListener` callbacks. 3. `:data` (`GeoJsonLayer`, `KmlLayer`, `MapViewRenderer`): Route markers, polygons, polylines, and ground overlays through passed-in `MarkerManager`, `PolygonManager`, `PolylineManager`, and `GroundOverlayManager` collections when provided, and register click listeners on those collections instead of clobbering global `GoogleMap` click listeners. 4. `:data` (`KmlLayer`, `GeoJsonLayer`, `Style`, `MapViewRenderer`): Aggregate placemarks and ground overlays from `` containers in `hasPlacemarks()`, `getPlacemarks()`, `features`, and `getGroundOverlays()`, and preserve styles on `MultiGeometry` features via `CompositeStyle`. 5. `:data` (`GeoJsonLayer`, `Style`, `MapViewRenderer`): Propagate `GeoJsonPointStyle` `title`, `snippet`, `isDraggable`, `isFlat`, `isVisible`, `infoWindowAnchorU/V`, and custom `icon` (`BitmapDescriptor`) to `MarkerOptions` and `AdvancedMarkerOptions`. 6. `:heatmaps` (`HeatmapTileProvider`): Clamp `zoom` when indexing `maxIntensity` (preventing `ArrayIndexOutOfBoundsException` at zoom level 22) and clamp `bucketX`/`bucketY` to `0 until gridDim` so points on the inclusive upper boundary `maxX`/`maxY` do not throw `ArrayIndexOutOfBoundsException`. 7. `:heatmaps` (`HeatmapTileProvider`): Check `&& wrappedPoints.isEmpty()` before returning `TileProvider.NO_TILE` so cross-antimeridian points render across the International Date Line, and enforce `MIN_RADIUS..MAX_RADIUS` and `0.0..1.0` bounds validation in `setRadius` and `setOpacity`. 8. `:clustering` (`DefaultClusterRenderer`): Clear stale `marker.title` and `marker.snippet` when a `ClusterItem`'s title/snippet is updated to `null`, and update `marker.zIndex` in `onClusterItemUpdated` even when `position` is unchanged. 9. `:library` (`MapObjectManager`): Invoke `setListenersOnUiThread()` synchronously when constructed on the main thread instead of unconditionally posting to the back of the main looper queue. 10. `:ui` (`AnimationUtil`, `IconGenerator`, `RotationLayout`): Snap directly to `finalPosition` when `durationInMs <= 0L` (avoiding `0 / 0.0f = NaN`), clamp `t` to `[0f, 1f]` so `AccelerateDecelerateInterpolator` never rebounds on frame overshoot, and use `degrees.mod(360)` so negative multi-turn rotations do not throw `IllegalStateException`. * fix: address PR review comments on layer managers, KML accessors, Style, and cluster renderers --- MIGRATION.md | 10 + .../view/ClusterRendererMultipleItems.kt | 34 +- .../DefaultAdvancedMarkersClusterRenderer.kt | 34 +- .../clustering/view/DefaultClusterRenderer.kt | 34 +- .../android/clustering/ClusterManagerTest.kt | 47 +++ .../maps/android/data/geojson/GeoJsonLayer.kt | 214 +++++++++--- .../google/maps/android/data/kml/KmlLayer.kt | 219 +++++++++--- .../data/renderer/mapview/MapViewRenderer.kt | 330 +++++++++++------- .../maps/android/data/renderer/model/Style.kt | 71 ++-- .../data/geojson/GeoJsonLayerOnMapTest.kt | 78 +++++ .../android/data/kml/KmlLayerOnMapTest.kt | 64 ++++ .../android/heatmaps/HeatmapTileProvider.kt | 21 +- .../heatmaps/HeatmapTileProviderTest.kt | 63 ++++ .../android/collections/MapObjectManager.kt | 7 +- .../collections/MapObjectManagerTest.kt | 22 ++ .../google/maps/android/ui/AnimationUtil.kt | 9 +- .../google/maps/android/ui/IconGenerator.kt | 2 +- .../google/maps/android/ui/RotationLayout.kt | 2 +- .../google/maps/android/AnimationUtilTest.kt | 18 + .../maps/android/ui/IconGeneratorTest.kt | 10 + 20 files changed, 994 insertions(+), 295 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 1deaca3a6..d41565daa 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -109,3 +109,13 @@ geoJsonLayer.setOnFeatureClickListener { feature -> } ``` Unlike the previous version, this SAM conversion is fully supported for native Kotlin callers without requiring anonymous object syntax (`object : Layer.OnFeatureClickListener { ... }`). + +--- + +## 4. Data Renderer Model Style Updates (v6.0.0) + +In `v6.0.0`, the `com.google.maps.android.data.renderer.model.Style` hierarchy was expanded to support complete feature styling and heterogeneous multi-geometries: + +- **`PointStyle`**, **`LineStyle`**, and **`PolygonStyle`**: Added default-valued constructor properties (`title`, `snippet`, `draggable`, `flat`, `visible`, `infoWindowAnchorU`, and `infoWindowAnchorV` on `PointStyle`; `clickable` and `visible` on `LineStyle` and `PolygonStyle`). Source calls with named or positional arguments continue to compile unchanged, though recompilation is required against the updated data class `copy()` and constructor signatures. +- **`CompositeStyle`**: Added `CompositeStyle(pointStyle, lineStyle, polygonStyle)` as a new subtype of the `sealed interface Style` to support heterogeneous `MultiGeometry` features. Exhaustive `when (style)` expressions over `Style` in consumer code should add a `is CompositeStyle ->` branch (or an `else ->` branch). + diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt index 15356bfb7..b65ea8f0e 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt @@ -904,7 +904,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct marker: Marker, ) { var changed = false - // Update marker text if the item text changed - same logic as adding marker in CreateMarkerTask.perform() + // Update marker text if the item text changed - same logic as adding marker in onBeforeClusterItemRendered() if (item.title != null && item.snippet != null) { if (item.title != marker.title) { marker.title = item.title @@ -914,19 +914,33 @@ open class ClusterRendererMultipleItems @JvmOverloads construct marker.snippet = item.snippet changed = true } - } else if (item.snippet != null && item.snippet != marker.title) { - marker.title = item.snippet - changed = true - } else if (item.title != null && item.title != marker.title) { - marker.title = item.title - changed = true + } else if (item.snippet != null) { + if (item.snippet != marker.title) { + marker.title = item.snippet + changed = true + } + if (marker.snippet != null) { + marker.snippet = null + changed = true + } + } else if (item.title != null) { + if (item.title != marker.title) { + marker.title = item.title + changed = true + } + if (marker.snippet != null) { + marker.snippet = null + changed = true + } } // Update marker position if the item changed position if (marker.position != item.position) { marker.position = item.position - if (item.zIndex != null) { - marker.zIndex = item.zIndex!! - } + changed = true + } + val itemZIndex = item.zIndex + if (itemZIndex != null && marker.zIndex != itemZIndex) { + marker.zIndex = itemZIndex changed = true } if (changed && marker.isInfoWindowShown) { diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt index c36d6fa5c..86903c2ef 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt @@ -833,7 +833,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads marker: Marker, ) { var changed = false - // Update marker text if the item text changed - same logic as adding marker in CreateMarkerTask.perform() + // Update marker text if the item text changed - same logic as adding marker in onBeforeClusterItemRendered() if (item.title != null && item.snippet != null) { if (item.title != marker.title) { marker.title = item.title @@ -843,19 +843,33 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads marker.snippet = item.snippet changed = true } - } else if (item.snippet != null && item.snippet != marker.title) { - marker.title = item.snippet - changed = true - } else if (item.title != null && item.title != marker.title) { - marker.title = item.title - changed = true + } else if (item.snippet != null) { + if (item.snippet != marker.title) { + marker.title = item.snippet + changed = true + } + if (marker.snippet != null) { + marker.snippet = null + changed = true + } + } else if (item.title != null) { + if (item.title != marker.title) { + marker.title = item.title + changed = true + } + if (marker.snippet != null) { + marker.snippet = null + changed = true + } } // Update marker position if the item changed position if (marker.position != item.position) { marker.position = item.position - if (item.zIndex != null) { - marker.zIndex = item.zIndex!! - } + changed = true + } + val itemZIndex = item.zIndex + if (itemZIndex != null && marker.zIndex != itemZIndex) { + marker.zIndex = itemZIndex changed = true } if (changed && marker.isInfoWindowShown) { diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt index ed2633829..ba77a72b6 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt @@ -835,7 +835,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( marker: Marker, ) { var changed = false - // Update marker text if the item text changed - same logic as adding marker in CreateMarkerTask.perform() + // Update marker text if the item text changed - same logic as adding marker in onBeforeClusterItemRendered() if (item.title != null && item.snippet != null) { if (item.title != marker.title) { marker.title = item.title @@ -845,19 +845,33 @@ open class DefaultClusterRenderer @JvmOverloads constructor( marker.snippet = item.snippet changed = true } - } else if (item.snippet != null && item.snippet != marker.title) { - marker.title = item.snippet - changed = true - } else if (item.title != null && item.title != marker.title) { - marker.title = item.title - changed = true + } else if (item.snippet != null) { + if (item.snippet != marker.title) { + marker.title = item.snippet + changed = true + } + if (marker.snippet != null) { + marker.snippet = null + changed = true + } + } else if (item.title != null) { + if (item.title != marker.title) { + marker.title = item.title + changed = true + } + if (marker.snippet != null) { + marker.snippet = null + changed = true + } } // Update marker position if the item changed position if (marker.position != item.position) { marker.position = item.position - if (item.zIndex != null) { - marker.zIndex = item.zIndex!! - } + changed = true + } + val itemZIndex = item.zIndex + if (itemZIndex != null && marker.zIndex != itemZIndex) { + marker.zIndex = itemZIndex changed = true } if (changed && marker.isInfoWindowShown) { diff --git a/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerTest.kt b/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerTest.kt index 030350810..756d465d4 100644 --- a/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerTest.kt +++ b/clustering/src/test/java/com/google/maps/android/clustering/ClusterManagerTest.kt @@ -117,4 +117,51 @@ class ClusterManagerTest { clusterManager.setOnClusterInfoWindowClickListener(clusterInfoClickListener) clusterManager.setOnClusterItemInfoWindowClickListener(itemInfoClickListener) } + + private class MutableTestItem( + override var position: LatLng, + override var title: String?, + override var snippet: String?, + override var zIndex: Float?, + ) : ClusterItem + + private class TestableDefaultClusterRenderer( + context: Context, + map: GoogleMap, + clusterManager: ClusterManager, + ) : com.google.maps.android.clustering.view.DefaultClusterRenderer(context, map, clusterManager) { + fun invokeOnClusterItemUpdated( + item: MutableTestItem, + marker: Marker, + ) { + onClusterItemUpdated(item, marker) + } + } + + @Test + fun testOnClusterItemUpdated_clearsNullSnippetAndUpdatesZIndex() { + val mutableClusterManager = ClusterManager(context, map, markerManager) + val renderer = TestableDefaultClusterRenderer(context, map, mutableClusterManager) + val mockMarker = mockk(relaxed = true) + var markerPosition = LatLng(37.7749, -122.4194) + var markerTitle: String? = "Initial Title" + var markerSnippet: String? = "Initial Snippet" + var markerZIndex = 1.0f + + every { mockMarker.position } answers { markerPosition } + every { mockMarker.setPosition(any()) } answers { markerPosition = firstArg() } + every { mockMarker.title } answers { markerTitle } + every { mockMarker.setTitle(any()) } answers { markerTitle = firstArg() } + every { mockMarker.snippet } answers { markerSnippet } + every { mockMarker.setSnippet(any()) } answers { markerSnippet = firstArg() } + every { mockMarker.zIndex } answers { markerZIndex } + every { mockMarker.setZIndex(any()) } answers { markerZIndex = firstArg() } + every { mockMarker.isInfoWindowShown } returns false + + val item = MutableTestItem(LatLng(37.7749, -122.4194), "Initial Title", null, 10.0f) + renderer.invokeOnClusterItemUpdated(item, mockMarker) + + assertThat(markerSnippet).isNull() + assertThat(markerZIndex).isEqualTo(10.0f) + } } diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt index af09642bf..95102b3c1 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt @@ -40,6 +40,7 @@ import com.google.maps.android.data.parser.geojson.GeoJsonPoint as ParserGeoJson import com.google.maps.android.data.parser.geojson.GeoJsonPolygon as ParserGeoJsonPolygon import com.google.maps.android.data.renderer.UrlIconProvider import com.google.maps.android.data.renderer.mapview.MapViewRenderer +import com.google.maps.android.data.renderer.model.CompositeStyle as ModelCompositeStyle import com.google.maps.android.data.renderer.model.Feature as ModelFeature import com.google.maps.android.data.renderer.model.Geometry as ModelGeometry import com.google.maps.android.data.renderer.model.LineString as ModelLineString @@ -66,6 +67,16 @@ public class GeoJsonLayer : Layer { private var mIsLayerOnMap = false private val mFeatureMap = HashMap() private val mModelToLegacyFeatures = IdentityHashMap() + private var mMarkerManager: MarkerManager? = null + private var mPolygonManager: PolygonManager? = null + private var mPolylineManager: PolylineManager? = null + private var mGroundOverlayManager: GroundOverlayManager? = null + private var mManagersMap: GoogleMap? = null + private var mMarkerCollection: MarkerManager.Collection? = null + private var mPolygonCollection: PolygonManager.Collection? = null + private var mPolylineCollection: PolylineManager.Collection? = null + private var mGroundOverlayCollection: GroundOverlayManager.Collection? = null + private val mCachedDescriptors = HashMap() private var mFeatureClickListener: OnFeatureClickListener? = null private val mFeatureObserver = Observer { observable, _ -> @@ -85,6 +96,10 @@ public class GeoJsonLayer : Layer { groundOverlayManager: GroundOverlayManager? = null, ) { mGoogleMap = map + mMarkerManager = markerManager + mPolygonManager = polygonManager + mPolylineManager = polylineManager + mGroundOverlayManager = groundOverlayManager val stream = geoJsonFile.toString().byteInputStream() parseGeoJson(stream) initializeRenderer(map) @@ -102,6 +117,10 @@ public class GeoJsonLayer : Layer { groundOverlayManager: GroundOverlayManager? = null, ) { mGoogleMap = map + mMarkerManager = markerManager + mPolygonManager = polygonManager + mPolylineManager = polylineManager + mGroundOverlayManager = groundOverlayManager val stream = context.resources.openRawResource(resourceId) parseGeoJson(stream) initializeRenderer(map) @@ -157,7 +176,32 @@ public class GeoJsonLayer : Layer { } private fun initializeRenderer(map: GoogleMap?) { - mRenderer = map?.let { MapViewRenderer(it, UrlIconProvider()) } + if (map == null) { + mRenderer = null + return + } + if (mManagersMap == null && + (mMarkerManager != null || mPolygonManager != null || mPolylineManager != null || mGroundOverlayManager != null) + ) { + mManagersMap = map + mMarkerCollection = mMarkerManager?.newCollection() + mPolygonCollection = mPolygonManager?.newCollection() + mPolylineCollection = mPolylineManager?.newCollection() + mGroundOverlayCollection = mGroundOverlayManager?.newCollection() + } + val renderer = + MapViewRenderer( + map = map, + iconProvider = UrlIconProvider(), + markerCollection = mMarkerCollection, + polygonCollection = mPolygonCollection, + polylineCollection = mPolylineCollection, + groundOverlayCollection = mGroundOverlayCollection, + ) + mCachedDescriptors.forEach { (key, descriptor) -> + renderer.cacheIconDescriptor(key, descriptor) + } + mRenderer = renderer } private fun toLegacyGeometry(geometry: ParserGeoJsonGeometry): Geometry = @@ -247,13 +291,28 @@ public class GeoJsonLayer : Layer { override fun getMap(): GoogleMap? = mGoogleMap override fun setMap(map: GoogleMap?) { + val wasLayerOnMap = mIsLayerOnMap + if (wasLayerOnMap) { + removeLayerFromMap() + } + if (map != null && mManagersMap != null && map !== mManagersMap) { + mMarkerManager = null + mPolygonManager = null + mPolylineManager = null + mGroundOverlayManager = null + mMarkerCollection = null + mPolygonCollection = null + mPolylineCollection = null + mGroundOverlayCollection = null + mManagersMap = null + } mGoogleMap = map if (map == null) { - removeLayerFromMap() mRenderer = null } else { - mRenderer = MapViewRenderer(map, UrlIconProvider()) - if (mIsLayerOnMap) { + initializeRenderer(map) + mFeatureClickListener?.let { setOnFeatureClickListener(it) } + if (wasLayerOnMap) { addLayerToMap() } } @@ -275,6 +334,60 @@ public class GeoJsonLayer : Layer { mIsLayerOnMap = false } + private fun buildPointStyle(feature: GeoJsonFeature): ModelPointStyle { + val pointStyle = feature.pointStyle ?: mDefaultPointStyle + val iconUrl = + pointStyle.getIcon()?.let { descriptor -> + val key = "geojson-descriptor://${System.identityHashCode(descriptor)}" + mCachedDescriptors[key] = descriptor + mRenderer?.cacheIconDescriptor(key, descriptor) + key + } + return ModelPointStyle( + // The renderer derives the marker's alpha from the color's alpha channel, so encode the + // legacy style's alpha into an otherwise-black color (hue 0 keeps the default marker look). + // A transparent color here (e.g. 0) would render the marker invisible. + color = Color.argb((pointStyle.getAlpha() * 255).toInt().coerceIn(0, 255), 0, 0, 0), + iconUrl = iconUrl, + anchorU = pointStyle.getAnchorU(), + anchorV = pointStyle.getAnchorV(), + heading = pointStyle.getRotation(), + zIndex = pointStyle.getZIndex(), + title = pointStyle.getTitle(), + snippet = pointStyle.getSnippet(), + draggable = pointStyle.isDraggable(), + flat = pointStyle.isFlat(), + visible = pointStyle.isVisible(), + infoWindowAnchorU = pointStyle.getInfoWindowAnchorU(), + infoWindowAnchorV = pointStyle.getInfoWindowAnchorV(), + ) + } + + private fun buildLineStyle(feature: GeoJsonFeature): ModelLineStyle { + val lineStyle = feature.lineStringStyle ?: mDefaultLineStringStyle + return ModelLineStyle( + color = lineStyle.color, + width = lineStyle.getWidth(), + geodesic = lineStyle.isGeodesic(), + zIndex = lineStyle.getZIndex(), + clickable = lineStyle.isClickable(), + visible = lineStyle.isVisible(), + ) + } + + private fun buildPolygonStyle(feature: GeoJsonFeature): ModelPolygonStyle { + val polygonStyle = feature.polygonStyle ?: mDefaultPolygonStyle + return ModelPolygonStyle( + fillColor = polygonStyle.fillColor, + strokeColor = polygonStyle.getStrokeColor(), + strokeWidth = polygonStyle.getStrokeWidth(), + geodesic = polygonStyle.isGeodesic(), + zIndex = polygonStyle.getZIndex(), + clickable = polygonStyle.isClickable(), + visible = polygonStyle.isVisible(), + ) + } + private fun toModelFeature(feature: GeoJsonFeature): ModelFeature { val existing = mFeatureMap[feature] if (existing != null) return existing @@ -285,57 +398,23 @@ public class GeoJsonLayer : Layer { val style = when (modelGeometry) { - is ModelPointGeometry -> { - val pointStyle = feature.pointStyle ?: mDefaultPointStyle - ModelPointStyle( - // The renderer derives the marker's alpha from the color's alpha channel, so encode the - // legacy style's alpha into an otherwise-black color (hue 0 keeps the default marker look). - // A transparent color here (e.g. 0) would render the marker invisible. - color = Color.argb((pointStyle.getAlpha() * 255).toInt().coerceIn(0, 255), 0, 0, 0), - anchorU = pointStyle.getAnchorU(), - anchorV = pointStyle.getAnchorV(), - heading = pointStyle.getRotation(), - zIndex = pointStyle.getZIndex(), - ) - } - - is ModelLineString -> { - val lineStyle = feature.lineStringStyle ?: mDefaultLineStringStyle - ModelLineStyle( - color = lineStyle.color, - width = lineStyle.getWidth(), - geodesic = lineStyle.isGeodesic(), - zIndex = lineStyle.getZIndex(), - ) - } - - is ModelPolygon -> { - val polygonStyle = feature.polygonStyle ?: mDefaultPolygonStyle - ModelPolygonStyle( - fillColor = polygonStyle.fillColor, - strokeColor = polygonStyle.getStrokeColor(), - strokeWidth = polygonStyle.getStrokeWidth(), - geodesic = polygonStyle.isGeodesic(), - zIndex = polygonStyle.getZIndex(), - ) - } - + is ModelPointGeometry -> buildPointStyle(feature) + is ModelLineString -> buildLineStyle(feature) + is ModelPolygon -> buildPolygonStyle(feature) is ModelMultiGeometry -> { - if (geometry is GeoJsonMultiPolygon) { - val polygonStyle = feature.polygonStyle ?: mDefaultPolygonStyle - ModelPolygonStyle( - fillColor = polygonStyle.fillColor, - strokeColor = polygonStyle.getStrokeColor(), - strokeWidth = polygonStyle.getStrokeWidth(), - geodesic = polygonStyle.isGeodesic(), - zIndex = polygonStyle.getZIndex(), - ) - } else null - } - - else -> { - null + when (geometry) { + is GeoJsonMultiPolygon -> buildPolygonStyle(feature) + is GeoJsonMultiLineString -> buildLineStyle(feature) + is GeoJsonMultiPoint -> buildPointStyle(feature) + else -> + ModelCompositeStyle( + pointStyle = buildPointStyle(feature), + lineStyle = buildLineStyle(feature), + polygonStyle = buildPolygonStyle(feature), + ) + } } + else -> null } val modelFeature = ModelFeature(modelGeometry, style, properties) @@ -430,26 +509,47 @@ public class GeoJsonLayer : Layer { override fun setOnFeatureClickListener(listener: OnFeatureClickListener) { mFeatureClickListener = listener - mGoogleMap?.let { map -> - map.setOnMarkerClickListener { marker -> + val renderer = mRenderer + val map = mGoogleMap ?: return + + val markerClickListener = + GoogleMap.OnMarkerClickListener { marker -> val feature = findLegacyFeatureForMapObject(marker) if (feature != null) { mFeatureClickListener?.onFeatureClick(feature) } false } - map.setOnPolygonClickListener { polygon -> + if (renderer?.markerCollection != null) { + renderer.markerCollection.setOnMarkerClickListener(markerClickListener) + } else { + map.setOnMarkerClickListener(markerClickListener) + } + + val polygonClickListener = + GoogleMap.OnPolygonClickListener { polygon -> val feature = findLegacyFeatureForMapObject(polygon) if (feature != null) { mFeatureClickListener?.onFeatureClick(feature) } } - map.setOnPolylineClickListener { polyline -> + if (renderer?.polygonCollection != null) { + renderer.polygonCollection.setOnPolygonClickListener(polygonClickListener) + } else { + map.setOnPolygonClickListener(polygonClickListener) + } + + val polylineClickListener = + GoogleMap.OnPolylineClickListener { polyline -> val feature = findLegacyFeatureForMapObject(polyline) if (feature != null) { mFeatureClickListener?.onFeatureClick(feature) } } + if (renderer?.polylineCollection != null) { + renderer.polylineCollection.setOnPolylineClickListener(polylineClickListener) + } else { + map.setOnPolylineClickListener(polylineClickListener) } } diff --git a/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt b/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt index 5e0373414..3eac2d5c3 100644 --- a/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt +++ b/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt @@ -45,8 +45,19 @@ public class KmlLayer : Layer { private var mRenderer: MapViewRenderer? = null private var mIsLayerOnMap = false private val mPlacemarkMap = HashMap() + private val mGroundOverlayMap = HashMap() private val mModelToLegacyPlacemarks = HashMap() private var mFeatureClickListener: OnFeatureClickListener? = null + private var mMarkerManager: MarkerManager? = null + private var mPolygonManager: PolygonManager? = null + private var mPolylineManager: PolylineManager? = null + private var mGroundOverlayManager: GroundOverlayManager? = null + private var mManagersMap: GoogleMap? = null + private var mMarkerCollection: MarkerManager.Collection? = null + private var mPolygonCollection: PolygonManager.Collection? = null + private var mPolylineCollection: PolylineManager.Collection? = null + private var mGroundOverlayCollection: GroundOverlayManager.Collection? = null + private val mCachedImages = HashMap() @JvmOverloads @Throws(XmlPullParserException::class, IOException::class) @@ -89,7 +100,11 @@ public class KmlLayer : Layer { maxKmzUncompressedTotalSize: Long = 50 * 1024 * 1024, ) { mGoogleMap = map - mRenderer = map?.let { MapViewRenderer(it, UrlIconProvider()) } + mMarkerManager = markerManager + mPolygonManager = polygonManager + mPolylineManager = polylineManager + mGroundOverlayManager = groundOverlayManager + initializeRenderer(map) val bis = BufferedInputStream(stream) bis.mark(1024) @@ -110,6 +125,7 @@ public class KmlLayer : Layer { // Register KMZ cached images to renderer if any kmlObj.images.forEach { (name, bitmap) -> + mCachedImages[name] = bitmap mRenderer?.cacheImageData(name, bitmap) } @@ -326,16 +342,60 @@ public class KmlLayer : Layer { return (a shl 24) or (r shl 16) or (g shl 8) or b } + private fun initializeRenderer(map: GoogleMap?) { + if (map == null) { + mRenderer = null + return + } + if (mManagersMap == null && + (mMarkerManager != null || mPolygonManager != null || mPolylineManager != null || mGroundOverlayManager != null) + ) { + mManagersMap = map + mMarkerCollection = mMarkerManager?.newCollection() + mPolygonCollection = mPolygonManager?.newCollection() + mPolylineCollection = mPolylineManager?.newCollection() + mGroundOverlayCollection = mGroundOverlayManager?.newCollection() + } + val renderer = + MapViewRenderer( + map = map, + iconProvider = UrlIconProvider(), + markerCollection = mMarkerCollection, + polygonCollection = mPolygonCollection, + polylineCollection = mPolylineCollection, + groundOverlayCollection = mGroundOverlayCollection, + ) + mCachedImages.forEach { (name, bitmap) -> + renderer.cacheImageData(name, bitmap) + } + mRenderer = renderer + } + override fun getMap(): GoogleMap? = mGoogleMap override fun setMap(map: GoogleMap?) { + val wasLayerOnMap = mIsLayerOnMap + if (wasLayerOnMap) { + removeLayerFromMap() + } + if (map != null && mManagersMap != null && map !== mManagersMap) { + mMarkerManager = null + mPolygonManager = null + mPolylineManager = null + mGroundOverlayManager = null + mMarkerCollection = null + mPolygonCollection = null + mPolylineCollection = null + mGroundOverlayCollection = null + mManagersMap = null + } mGoogleMap = map if (map == null) { - removeLayerFromMap() mRenderer = null } else { - mRenderer = MapViewRenderer(map, UrlIconProvider()) - if (mIsLayerOnMap) { + initializeRenderer(map) + mFeatureClickListener?.let { setOnFeatureClickListener(it) } + if (wasLayerOnMap) { addLayerToMap() } } @@ -375,6 +435,39 @@ public class KmlLayer : Layer { container.getContainers().forEach { c -> removeContainerFromMap(c, renderer) } } + private fun buildPointStyle(inline: KmlStyle?): com.google.maps.android.data.renderer.model.PointStyle = + com.google.maps.android.data.renderer.model.PointStyle( + // mMarkerColor is a hue (0..360), not an ARGB color — convert it before handing it to the + // renderer, which derives the marker's hue and alpha from an ARGB value. Passing the raw + // hue (or 0) made the alpha channel 0 and rendered every KML point marker invisible. + color = + inline?.mMarkerColor?.let { hue -> Color.HSVToColor(floatArrayOf(hue, 1f, 1f)) } + ?: Color.BLACK, + iconUrl = inline?.getIconUrl(), + zIndex = inline?.mMarkerOptions?.zIndex ?: 0f, + ) + + private fun buildLineStyle(inline: KmlStyle?): com.google.maps.android.data.renderer.model.LineStyle = + com.google.maps.android.data.renderer.model.LineStyle( + color = inline?.mPolylineOptions?.color ?: 0xFF000000.toInt(), + width = inline?.mPolylineOptions?.width ?: 1.0f, + geodesic = inline?.mPolylineOptions?.isGeodesic ?: false, + zIndex = inline?.mPolylineOptions?.zIndex ?: 0f, + clickable = inline?.mPolylineOptions?.isClickable ?: true, + visible = inline?.mPolylineOptions?.isVisible ?: true, + ) + + private fun buildPolygonStyle(inline: KmlStyle?): com.google.maps.android.data.renderer.model.PolygonStyle = + com.google.maps.android.data.renderer.model.PolygonStyle( + fillColor = inline?.mPolygonOptions?.fillColor ?: 0x00000000, + strokeColor = inline?.mPolygonOptions?.strokeColor ?: 0xFF000000.toInt(), + strokeWidth = inline?.mPolygonOptions?.strokeWidth ?: 1.0f, + geodesic = inline?.mPolygonOptions?.isGeodesic ?: false, + zIndex = inline?.mPolygonOptions?.zIndex ?: 0f, + clickable = inline?.mPolygonOptions?.isClickable ?: true, + visible = inline?.mPolygonOptions?.isVisible ?: true, + ) + private fun toModelFeature(placemark: KmlPlacemark): com.google.maps.android.data.renderer.model.Feature { val existing = mPlacemarkMap[placemark] if (existing != null) return existing @@ -386,41 +479,16 @@ public class KmlLayer : Layer { val inline = placemark.getInlineStyle() val style = when (modelGeometry) { - is com.google.maps.android.data.renderer.model.PointGeometry -> { - com.google.maps.android.data.renderer.model.PointStyle( - // mMarkerColor is a hue (0..360), not an ARGB color — convert it before handing it to the - // renderer, which derives the marker's hue and alpha from an ARGB value. Passing the raw - // hue (or 0) made the alpha channel 0 and rendered every KML point marker invisible. - color = - inline?.mMarkerColor?.let { hue -> Color.HSVToColor(floatArrayOf(hue, 1f, 1f)) } - ?: Color.BLACK, - iconUrl = inline?.getIconUrl(), - zIndex = inline?.mMarkerOptions?.zIndex ?: 0f, - ) - } - - is com.google.maps.android.data.renderer.model.LineString -> { - com.google.maps.android.data.renderer.model.LineStyle( - color = inline?.mPolylineOptions?.color ?: 0xFF000000.toInt(), - width = inline?.mPolylineOptions?.width ?: 1.0f, - geodesic = inline?.mPolylineOptions?.isGeodesic ?: false, - zIndex = inline?.mPolylineOptions?.zIndex ?: 0f, + is com.google.maps.android.data.renderer.model.PointGeometry -> buildPointStyle(inline) + is com.google.maps.android.data.renderer.model.LineString -> buildLineStyle(inline) + is com.google.maps.android.data.renderer.model.Polygon -> buildPolygonStyle(inline) + is com.google.maps.android.data.renderer.model.MultiGeometry -> + com.google.maps.android.data.renderer.model.CompositeStyle( + pointStyle = buildPointStyle(inline), + lineStyle = buildLineStyle(inline), + polygonStyle = buildPolygonStyle(inline), ) - } - - is com.google.maps.android.data.renderer.model.Polygon -> { - com.google.maps.android.data.renderer.model.PolygonStyle( - fillColor = inline?.mPolygonOptions?.fillColor ?: 0x00000000, - strokeColor = inline?.mPolygonOptions?.strokeColor ?: 0xFF000000.toInt(), - strokeWidth = inline?.mPolygonOptions?.strokeWidth ?: 1.0f, - geodesic = inline?.mPolygonOptions?.isGeodesic ?: false, - zIndex = inline?.mPolygonOptions?.zIndex ?: 0f, - ) - } - - else -> { - null - } + else -> null } val modelFeature = @@ -432,6 +500,9 @@ public class KmlLayer : Layer { } private fun toModelFeature(groundOverlay: KmlGroundOverlay): com.google.maps.android.data.renderer.model.Feature { + val existing = mGroundOverlayMap[groundOverlay] + if (existing != null) return existing + val bounds = groundOverlay.getLatLngBox() val modelGeometry = com.google.maps.android.data.renderer.model.GroundOverlay( @@ -448,8 +519,11 @@ public class KmlLayer : Layer { visibility = groundOverlay.getGroundOverlayOptions().isVisible, ) val properties = groundOverlay.getProperties().associateWith { groundOverlay.getProperty(it) as Any } - return com.google.maps.android.data.renderer.model - .Feature(modelGeometry, style, properties) + val modelFeature = + com.google.maps.android.data.renderer.model + .Feature(modelGeometry, style, properties) + mGroundOverlayMap[groundOverlay] = modelFeature + return modelFeature } private fun toModelGeometry(geometry: Geometry): com.google.maps.android.data.renderer.model.Geometry = @@ -501,6 +575,42 @@ public class KmlLayer : Layer { } } + private fun collectPlacemarks( + container: KmlContainer, + out: MutableList, + ) { + out.addAll(container.getPlacemarks()) + container.getContainers().forEach { collectPlacemarks(it, out) } + } + + /** + * Returns all [KmlPlacemark] objects in this layer, including both top-level placemarks + * and placemarks nested inside `` and `` [KmlContainer]s. + */ + public fun getAllPlacemarks(): List { + val all = ArrayList(mPlacemarks) + mContainers.forEach { collectPlacemarks(it, all) } + return all + } + + private fun collectGroundOverlays( + container: KmlContainer, + out: MutableList, + ) { + out.addAll(container.getGroundOverlays()) + container.getContainers().forEach { collectGroundOverlays(it, out) } + } + + /** + * Returns all [KmlGroundOverlay] objects in this layer, including both top-level ground overlays + * and ground overlays nested inside `` and `` [KmlContainer]s. + */ + public fun getAllGroundOverlays(): List { + val all = ArrayList(mGroundOverlays) + mContainers.forEach { collectGroundOverlays(it, all) } + return all + } + public fun hasPlacemarks(): Boolean = mPlacemarks.isNotEmpty() public fun getPlacemarks(): Iterable = mPlacemarks @@ -518,26 +628,47 @@ public class KmlLayer : Layer { override fun setOnFeatureClickListener(listener: OnFeatureClickListener) { mFeatureClickListener = listener - mGoogleMap?.let { map -> - map.setOnMarkerClickListener { marker -> + val renderer = mRenderer + val map = mGoogleMap ?: return + + val markerClickListener = + GoogleMap.OnMarkerClickListener { marker -> val feature = findLegacyFeatureForMapObject(marker) if (feature != null) { mFeatureClickListener?.onFeatureClick(feature) } false } - map.setOnPolygonClickListener { polygon -> + if (renderer?.markerCollection != null) { + renderer.markerCollection.setOnMarkerClickListener(markerClickListener) + } else { + map.setOnMarkerClickListener(markerClickListener) + } + + val polygonClickListener = + GoogleMap.OnPolygonClickListener { polygon -> val feature = findLegacyFeatureForMapObject(polygon) if (feature != null) { mFeatureClickListener?.onFeatureClick(feature) } } - map.setOnPolylineClickListener { polyline -> + if (renderer?.polygonCollection != null) { + renderer.polygonCollection.setOnPolygonClickListener(polygonClickListener) + } else { + map.setOnPolygonClickListener(polygonClickListener) + } + + val polylineClickListener = + GoogleMap.OnPolylineClickListener { polyline -> val feature = findLegacyFeatureForMapObject(polyline) if (feature != null) { mFeatureClickListener?.onFeatureClick(feature) } } + if (renderer?.polylineCollection != null) { + renderer.polylineCollection.setOnPolylineClickListener(polylineClickListener) + } else { + map.setOnPolylineClickListener(polylineClickListener) } } diff --git a/data/src/main/java/com/google/maps/android/data/renderer/mapview/MapViewRenderer.kt b/data/src/main/java/com/google/maps/android/data/renderer/mapview/MapViewRenderer.kt index 6dc89ccc4..6696a71d4 100644 --- a/data/src/main/java/com/google/maps/android/data/renderer/mapview/MapViewRenderer.kt +++ b/data/src/main/java/com/google/maps/android/data/renderer/mapview/MapViewRenderer.kt @@ -24,8 +24,13 @@ import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.PinConfig import com.google.android.gms.maps.model.PolygonOptions import com.google.android.gms.maps.model.PolylineOptions +import com.google.maps.android.collections.GroundOverlayManager +import com.google.maps.android.collections.MarkerManager +import com.google.maps.android.collections.PolygonManager +import com.google.maps.android.collections.PolylineManager import com.google.maps.android.data.renderer.DataRenderer import com.google.maps.android.data.renderer.IconProvider +import com.google.maps.android.data.renderer.model.CompositeStyle import com.google.maps.android.data.renderer.model.DataLayer import com.google.maps.android.data.renderer.model.DataScene import com.google.maps.android.data.renderer.model.Feature @@ -55,138 +60,174 @@ import java.util.IdentityHashMap * * @property map The [GoogleMap] instance to render features on. */ -class MapViewRenderer( - private val map: GoogleMap, - private val iconProvider: IconProvider, -) : DataRenderer { - // Scope for all coroutines launched by this renderer. - // Using SupervisorJob so that failure of one icon load doesn't cancel others. - private val rendererScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - - /** - * Controls whether to use the new Advanced Markers API (if available) or legacy Markers. - * Default is false (legacy Markers). - */ - var useAdvancedMarkers: Boolean = false - - // Cache for local images (e.g. from KMZ files) - private val localImages = mutableMapOf() +public class MapViewRenderer + internal constructor( + private val map: GoogleMap, + private val iconProvider: IconProvider, + internal val markerCollection: MarkerManager.Collection?, + internal val polygonCollection: PolygonManager.Collection?, + internal val polylineCollection: PolylineManager.Collection?, + internal val groundOverlayCollection: GroundOverlayManager.Collection?, + ) : DataRenderer { + @JvmOverloads + public constructor( + map: GoogleMap, + iconProvider: IconProvider, + markerManager: MarkerManager? = null, + polygonManager: PolygonManager? = null, + polylineManager: PolylineManager? = null, + groundOverlayManager: GroundOverlayManager? = null, + ) : this( + map = map, + iconProvider = iconProvider, + markerCollection = markerManager?.newCollection(), + polygonCollection = polygonManager?.newCollection(), + polylineCollection = polylineManager?.newCollection(), + groundOverlayCollection = groundOverlayManager?.newCollection(), + ) + + // Scope for all coroutines launched by this renderer. + // Using SupervisorJob so that failure of one icon load doesn't cancel others. + private val rendererScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + + /** + * Controls whether to use the new Advanced Markers API (if available) or legacy Markers. + * Default is false (legacy Markers). + */ + public var useAdvancedMarkers: Boolean = false + + // Cache for local images (e.g. from KMZ files) + private val localImages = mutableMapOf() + + // Cache for pre-constructed BitmapDescriptors (e.g. from legacy GeoJsonPointStyle) + private val localDescriptors = mutableMapOf() + + /** + * Caches an image for a specific URL. + * This is useful for KMZ files or other scenarios where images are loaded locally + * and should be used instead of fetching from the network. + * + * @param url The URL associated with the image. + * @param bitmap The bitmap to cache. + */ + public fun cacheImageData( + url: String, + bitmap: android.graphics.Bitmap, + ) { + localImages[url] = bitmap + } - /** - * Caches an image for a specific URL. - * This is useful for KMZ files or other scenarios where images are loaded locally - * and should be used instead of fetching from the network. - * - * @param url The URL associated with the image. - * @param bitmap The bitmap to cache. - */ - fun cacheImageData( - url: String, - bitmap: android.graphics.Bitmap, - ) { - localImages[url] = bitmap - } + internal fun cacheIconDescriptor( + url: String, + descriptor: com.google.android.gms.maps.model.BitmapDescriptor, + ) { + localDescriptors[url] = descriptor + } - // Track rendered map objects for each feature so we can remove them later - private val renderedFeatures = IdentityHashMap>() + // Track rendered map objects for each feature so we can remove them later + private val renderedFeatures = IdentityHashMap>() - override fun render(scene: DataScene) { - scene.layers.forEach { renderLayer(it) } - } + override fun render(scene: DataScene) { + scene.layers.forEach { renderLayer(it) } + } - override fun addLayer(layer: DataLayer) { - renderLayer(layer) - } + override fun addLayer(layer: DataLayer) { + renderLayer(layer) + } - override fun removeLayer(layer: DataLayer) { - layer.features.forEach { removeFeature(it) } - } + override fun removeLayer(layer: DataLayer) { + layer.features.forEach { removeFeature(it) } + } - private fun renderLayer(layer: DataLayer) { - layer.features.forEach { feature -> - addFeature(feature) + private fun renderLayer(layer: DataLayer) { + layer.features.forEach { feature -> + addFeature(feature) + } } - } - override fun addFeature(feature: Feature) { - removeFeature(feature) - val mapObjects = mutableListOf() - addGeometry(feature.geometry, feature, mapObjects) - if (mapObjects.isNotEmpty()) { - renderedFeatures[feature] = mapObjects + override fun addFeature(feature: Feature) { + removeFeature(feature) + val mapObjects = mutableListOf() + addGeometry(feature.geometry, feature, mapObjects) + if (mapObjects.isNotEmpty()) { + renderedFeatures[feature] = mapObjects + } } - } - /** - * Renders a single [geometry] (recursing into [MultiGeometry] members) with [feature]'s style and - * properties, accumulating every created map object into [mapObjects] so nested geometries stay - * associated with the original [feature] and can be removed later via [removeFeature]. - */ - private fun addGeometry( - geometry: Geometry, - feature: Feature, - mapObjects: MutableList, - ) { - when (geometry) { - is PointGeometry -> { - val point = geometry.point - val style = feature.style as? PointStyle - if (useAdvancedMarkers) { - val markerOptions = createAdvancedMarkerOptions(point, style, feature.properties) - val marker = map.addMarker(markerOptions)!! - mapObjects.add(marker) - style?.iconUrl?.let { url -> - val localBitmap = localImages[url] - if (localBitmap != null) { - marker.setIcon(BitmapDescriptorFactory.fromBitmap(localBitmap)) - } else { - rendererScope.launch { - val bitmap = iconProvider.loadIcon(url) - if (bitmap != null) { - try { - marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap)) - } catch (e: Exception) { - // Marker might have been removed or other issue + /** + * Renders a single [geometry] (recursing into [MultiGeometry] members) with [feature]'s style and + * properties, accumulating every created map object into [mapObjects] so nested geometries stay + * associated with the original [feature] and can be removed later via [removeFeature]. + */ + private fun addGeometry( + geometry: Geometry, + feature: Feature, + mapObjects: MutableList, + ) { + when (geometry) { + is PointGeometry -> { + val point = geometry.point + val style = (feature.style as? PointStyle) ?: (feature.style as? CompositeStyle)?.pointStyle + if (useAdvancedMarkers) { + val markerOptions = createAdvancedMarkerOptions(point, style, feature.properties) + val marker = (markerCollection?.addMarker(markerOptions) ?: map.addMarker(markerOptions))!! + mapObjects.add(marker) + style?.iconUrl?.let { url -> + if (localDescriptors.containsKey(url)) return@let + val localBitmap = localImages[url] + if (localBitmap != null) { + marker.setIcon(BitmapDescriptorFactory.fromBitmap(localBitmap)) + } else { + rendererScope.launch { + val bitmap = iconProvider.loadIcon(url) + if (bitmap != null) { + try { + marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap)) + } catch (e: Exception) { + // Marker might have been removed or other issue + } } } } } - } - } else { - val markerOptions = createMarkerOptions(point, style, feature.properties) - val marker = map.addMarker(markerOptions)!! - mapObjects.add(marker) - style?.iconUrl?.let { url -> - val localBitmap = localImages[url] - if (localBitmap != null) { - marker.setIcon(BitmapDescriptorFactory.fromBitmap(localBitmap)) - } else { - rendererScope.launch { - val bitmap = iconProvider.loadIcon(url) - if (bitmap != null) { - try { - marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap)) - } catch (e: Exception) { - // Marker might have been removed + } else { + val markerOptions = createMarkerOptions(point, style, feature.properties) + val marker = (markerCollection?.addMarker(markerOptions) ?: map.addMarker(markerOptions))!! + mapObjects.add(marker) + style?.iconUrl?.let { url -> + if (localDescriptors.containsKey(url)) return@let + val localBitmap = localImages[url] + if (localBitmap != null) { + marker.setIcon(BitmapDescriptorFactory.fromBitmap(localBitmap)) + } else { + rendererScope.launch { + val bitmap = iconProvider.loadIcon(url) + if (bitmap != null) { + try { + marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap)) + } catch (e: Exception) { + // Marker might have been removed + } } } } } } } - } is LineString -> { - val style = feature.style as? LineStyle + val style = (feature.style as? LineStyle) ?: (feature.style as? CompositeStyle)?.lineStyle val polylineOptions = createPolylineOptions(geometry, style) - mapObjects.add(map.addPolyline(polylineOptions)) + val polyline = polylineCollection?.addPolyline(polylineOptions) ?: map.addPolyline(polylineOptions) + mapObjects.add(polyline) } is Polygon -> { - val style = feature.style as? PolygonStyle + val style = (feature.style as? PolygonStyle) ?: (feature.style as? CompositeStyle)?.polygonStyle val polygonOptions = createPolygonOptions(geometry, style) - mapObjects.add(map.addPolygon(polygonOptions)) + val polygon = polygonCollection?.addPolygon(polygonOptions) ?: map.addPolygon(polygonOptions) + mapObjects.add(polygon) } is MultiGeometry -> { @@ -204,7 +245,7 @@ class MapViewRenderer( val localBitmap = localImages[url] if (localBitmap != null) { options.image(BitmapDescriptorFactory.fromBitmap(localBitmap)) - val mapOverlay = map.addGroundOverlay(options) + val mapOverlay = groundOverlayCollection?.addGroundOverlay(options) ?: map.addGroundOverlay(options) if (mapOverlay != null) { mapObjects.add(mapOverlay) } @@ -214,7 +255,7 @@ class MapViewRenderer( options.image(BitmapDescriptorFactory.fromBitmap(TRANSPARENT_BITMAP)) options.visible(false) // Hide until loaded - val mapOverlay = map.addGroundOverlay(options) + val mapOverlay = groundOverlayCollection?.addGroundOverlay(options) ?: map.addGroundOverlay(options) if (mapOverlay != null) { mapObjects.add(mapOverlay) rendererScope.launch { @@ -232,7 +273,7 @@ class MapViewRenderer( } } ?: run { // No iconUrl, add directly - val mapOverlay = map.addGroundOverlay(options) + val mapOverlay = groundOverlayCollection?.addGroundOverlay(options) ?: map.addGroundOverlay(options) if (mapOverlay != null) { mapObjects.add(mapOverlay) } @@ -243,12 +284,7 @@ class MapViewRenderer( override fun removeFeature(feature: Feature) { renderedFeatures[feature]?.forEach { mapObject -> - when (mapObject) { - is com.google.android.gms.maps.model.Marker -> mapObject.remove() - is com.google.android.gms.maps.model.Polyline -> mapObject.remove() - is com.google.android.gms.maps.model.Polygon -> mapObject.remove() - is com.google.android.gms.maps.model.GroundOverlay -> mapObject.remove() - } + removeMapObject(mapObject) } renderedFeatures.remove(feature) } @@ -256,7 +292,7 @@ class MapViewRenderer( /** * Finds the model Feature associated with a specific rendered Google Map object. */ - fun getFeatureForMapObject(mapObject: Any): Feature? = + public fun getFeatureForMapObject(mapObject: Any): Feature? = renderedFeatures.entries .firstOrNull { entry -> entry.value.contains(mapObject) @@ -265,16 +301,36 @@ class MapViewRenderer( override fun clear() { rendererScope.cancel() renderedFeatures.values.flatten().forEach { mapObject -> - when (mapObject) { - is com.google.android.gms.maps.model.Marker -> mapObject.remove() - is com.google.android.gms.maps.model.Polyline -> mapObject.remove() - is com.google.android.gms.maps.model.Polygon -> mapObject.remove() - is com.google.android.gms.maps.model.GroundOverlay -> mapObject.remove() - } + removeMapObject(mapObject) } renderedFeatures.clear() } + private fun removeMapObject(mapObject: Any) { + when (mapObject) { + is com.google.android.gms.maps.model.Marker -> { + if (markerCollection == null || !markerCollection.remove(mapObject)) { + mapObject.remove() + } + } + is com.google.android.gms.maps.model.Polyline -> { + if (polylineCollection == null || !polylineCollection.remove(mapObject)) { + mapObject.remove() + } + } + is com.google.android.gms.maps.model.Polygon -> { + if (polygonCollection == null || !polygonCollection.remove(mapObject)) { + mapObject.remove() + } + } + is com.google.android.gms.maps.model.GroundOverlay -> { + if (groundOverlayCollection == null || !groundOverlayCollection.remove(mapObject)) { + mapObject.remove() + } + } + } + } + /** * Creates the [MarkerOptions] for legacy markers. * @@ -289,19 +345,24 @@ class MapViewRenderer( ): MarkerOptions { val markerOptions = MarkerOptions().position(LatLng(point.lat, point.lng)) - // Sniff out title and description from properties to enable click interaction/Info Window - val title = (properties["title"] ?: properties["name"])?.toString() - val snippet = (properties["description"] ?: properties["snippet"])?.toString() + // Sniff out title and description from style or properties to enable click interaction/Info Window + val title = style?.title ?: (properties["title"] ?: properties["name"])?.toString() + val snippet = style?.snippet ?: (properties["description"] ?: properties["snippet"])?.toString() title?.let { markerOptions.title(it) } snippet?.let { markerOptions.snippet(it) } style?.let { - // Default icon if not loaded yet - markerOptions.icon(BitmapDescriptorFactory.defaultMarker(hueFromColor(it.color))) + // Use custom icon descriptor if cached under iconUrl, otherwise default marker hue + val cachedDescriptor = it.iconUrl?.let { url -> localDescriptors[url] } + markerOptions.icon(cachedDescriptor ?: BitmapDescriptorFactory.defaultMarker(hueFromColor(it.color))) it.heading?.let { heading -> markerOptions.rotation(heading) } markerOptions.anchor(it.anchorU, it.anchorV) + markerOptions.infoWindowAnchor(it.infoWindowAnchorU, it.infoWindowAnchorV) markerOptions.alpha(android.graphics.Color.alpha(it.color) / 255.0f) markerOptions.zIndex(it.zIndex) + markerOptions.draggable(it.draggable) + markerOptions.flat(it.flat) + markerOptions.visible(it.visible) } return markerOptions } @@ -319,9 +380,9 @@ class MapViewRenderer( ): AdvancedMarkerOptions { val markerOptions = AdvancedMarkerOptions().position(LatLng(point.lat, point.lng)) - // Sniff out title and description from properties to enable click interaction/Info Window - val title = (properties["title"] ?: properties["name"])?.toString() - val snippet = (properties["description"] ?: properties["snippet"])?.toString() + // Sniff out title and description from style or properties to enable click interaction/Info Window + val title = style?.title ?: (properties["title"] ?: properties["name"])?.toString() + val snippet = style?.snippet ?: (properties["description"] ?: properties["snippet"])?.toString() title?.let { markerOptions.title(it) } snippet?.let { markerOptions.snippet(it) } @@ -333,8 +394,13 @@ class MapViewRenderer( .setBackgroundColor(it.color) .setBorderColor(android.graphics.Color.WHITE) // Default border .build() - markerOptions.icon(BitmapDescriptorFactory.fromPinConfig(pinConfig)) + val cachedDescriptor = it.iconUrl?.let { url -> localDescriptors[url] } + markerOptions.icon(cachedDescriptor ?: BitmapDescriptorFactory.fromPinConfig(pinConfig)) markerOptions.zIndex(it.zIndex) + markerOptions.draggable(it.draggable) + markerOptions.flat(it.flat) + markerOptions.visible(it.visible) + markerOptions.infoWindowAnchor(it.infoWindowAnchorU, it.infoWindowAnchorV) // AdvancedMarkerOptions does not support rotation directly in the same way as MarkerOptions for flat icons, // but it supports collision behavior etc. @@ -360,11 +426,13 @@ class MapViewRenderer( ): PolylineOptions { val polylineOptions = PolylineOptions() lineString.points.map { LatLng(it.lat, it.lng) }.forEach { polylineOptions.add(it) } + polylineOptions.clickable(style?.clickable ?: true) style?.let { polylineOptions.color(it.color) polylineOptions.width(it.width) polylineOptions.geodesic(it.geodesic) polylineOptions.zIndex(it.zIndex) + polylineOptions.visible(it.visible) } return polylineOptions } @@ -386,12 +454,14 @@ class MapViewRenderer( polygon.innerBoundaries.forEach { innerBoundary -> polygonOptions.addHole(innerBoundary.map { LatLng(it.lat, it.lng) }) } + polygonOptions.clickable(style?.clickable ?: true) style?.let { polygonOptions.fillColor(it.fillColor) polygonOptions.strokeColor(it.strokeColor) polygonOptions.strokeWidth(it.strokeWidth) polygonOptions.geodesic(it.geodesic) polygonOptions.zIndex(it.zIndex) + polygonOptions.visible(it.visible) } return polygonOptions } @@ -417,7 +487,7 @@ class MapViewRenderer( return hsv[0] } - companion object { + public companion object { private val TRANSPARENT_BITMAP by lazy { android.graphics.Bitmap.createBitmap(1, 1, android.graphics.Bitmap.Config.ARGB_8888) } diff --git a/data/src/main/java/com/google/maps/android/data/renderer/model/Style.kt b/data/src/main/java/com/google/maps/android/data/renderer/model/Style.kt index d01d6f517..8fa306cac 100644 --- a/data/src/main/java/com/google/maps/android/data/renderer/model/Style.kt +++ b/data/src/main/java/com/google/maps/android/data/renderer/model/Style.kt @@ -20,7 +20,7 @@ package com.google.maps.android.data.renderer.model * * This is a platform-agnostic representation of styling that can be applied to a rendered shape. */ -sealed interface Style +public sealed interface Style /** * A data class representing the style for a PointGeometry. @@ -32,15 +32,29 @@ sealed interface Style * @property anchorV The V-coordinate of the icon's anchor point, as a ratio of the icon's height (0.0 to 1.0). Defaults to 1.0 (bottom). * @property scale The scale factor for the icon. Defaults to 1.0. * @property zIndex The z-index of the marker. Higher values are drawn above lower values. Defaults to 0.0. + * @property title Optional title override for the marker. + * @property snippet Optional snippet override for the marker. + * @property draggable Whether the marker is draggable. Defaults to false. + * @property flat Whether the marker should be rendered flat against the map. Defaults to false. + * @property visible Whether the marker is visible. Defaults to true. + * @property infoWindowAnchorU The U-coordinate of the info window anchor point. Defaults to 0.5. + * @property infoWindowAnchorV The V-coordinate of the info window anchor point. Defaults to 0.0. */ -data class PointStyle( - val color: Int = 0xFF000000.toInt(), // Default to black - val iconUrl: String? = null, - val heading: Float? = null, - val anchorU: Float = 0.5f, - val anchorV: Float = 1.0f, - val scale: Float = 1.0f, - val zIndex: Float = 0.0f, +public data class PointStyle( + public val color: Int = 0xFF000000.toInt(), // Default to black + public val iconUrl: String? = null, + public val heading: Float? = null, + public val anchorU: Float = 0.5f, + public val anchorV: Float = 1.0f, + public val scale: Float = 1.0f, + public val zIndex: Float = 0.0f, + public val title: String? = null, + public val snippet: String? = null, + public val draggable: Boolean = false, + public val flat: Boolean = false, + public val visible: Boolean = true, + public val infoWindowAnchorU: Float = 0.5f, + public val infoWindowAnchorV: Float = 0.0f, ) : Style /** @@ -50,12 +64,16 @@ data class PointStyle( * @property width The width of the line in pixels. Defaults to 1.0. * @property geodesic Indicates whether the line should be drawn as a geodesic (true) or a straight line (false). Defaults to false. * @property zIndex The z-index of the line. Higher values are drawn above lower values. Defaults to 0.0. + * @property clickable Indicates whether the line is clickable. Defaults to true. + * @property visible Indicates whether the line is visible. Defaults to true. */ -data class LineStyle( - val color: Int = 0xFF000000.toInt(), // Default to black - val width: Float = 1.0f, - val geodesic: Boolean = false, - val zIndex: Float = 0.0f, +public data class LineStyle( + public val color: Int = 0xFF000000.toInt(), // Default to black + public val width: Float = 1.0f, + public val geodesic: Boolean = false, + public val zIndex: Float = 0.0f, + public val clickable: Boolean = true, + public val visible: Boolean = true, ) : Style /** @@ -66,11 +84,24 @@ data class LineStyle( * @property strokeWidth The stroke width of the polygon in pixels. Defaults to 1.0. * @property geodesic Indicates whether the polygon's stroke should be drawn as a geodesic (true) or a straight line (false). Defaults to false. * @property zIndex The z-index of the polygon. Higher values are drawn above lower values. Defaults to 0.0. + * @property clickable Indicates whether the polygon is clickable. Defaults to true. + * @property visible Indicates whether the polygon is visible. Defaults to true. */ -data class PolygonStyle( - val fillColor: Int = 0x00000000, - val strokeColor: Int = 0xFF000000.toInt(), // Default to black - val strokeWidth: Float = 1.0f, - val geodesic: Boolean = false, - val zIndex: Float = 0.0f, +public data class PolygonStyle( + public val fillColor: Int = 0x00000000, + public val strokeColor: Int = 0xFF000000.toInt(), // Default to black + public val strokeWidth: Float = 1.0f, + public val geodesic: Boolean = false, + public val zIndex: Float = 0.0f, + public val clickable: Boolean = true, + public val visible: Boolean = true, +) : Style + +/** + * A composite style carrying point, line, and polygon styles for heterogeneous [MultiGeometry] features. + */ +public data class CompositeStyle( + public val pointStyle: PointStyle? = null, + public val lineStyle: LineStyle? = null, + public val polygonStyle: PolygonStyle? = null, ) : Style diff --git a/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerOnMapTest.kt b/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerOnMapTest.kt index 32e54b979..328f0de8b 100644 --- a/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerOnMapTest.kt +++ b/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerOnMapTest.kt @@ -54,4 +54,82 @@ class GeoJsonLayerOnMapTest { layer.removeLayerFromMap() assertFalse(layer.isLayerOnMap()) } + + @Test + fun addLayerToMap_routesThroughObjectManagersAndAppliesFullStyles() { + io.mockk.mockkStatic(com.google.android.gms.maps.model.BitmapDescriptorFactory::class) + io.mockk.every { com.google.android.gms.maps.model.BitmapDescriptorFactory.defaultMarker(any()) } returns mockk() + try { + val mockMap = mockk(relaxed = true) + val markerSlot = io.mockk.slot() + val polylineSlot = io.mockk.slot() + val polygonSlot = io.mockk.slot() + val mockMarker = mockk(relaxed = true) + io.mockk.every { mockMap.addMarker(capture(markerSlot)) } returns mockMarker + io.mockk.every { mockMap.addPolyline(capture(polylineSlot)) } returns mockk(relaxed = true) + io.mockk.every { mockMap.addPolygon(capture(polygonSlot)) } returns mockk(relaxed = true) + + val markerManager = com.google.maps.android.collections.MarkerManager(mockMap) + val polygonManager = com.google.maps.android.collections.PolygonManager(mockMap) + val polylineManager = com.google.maps.android.collections.PolylineManager(mockMap) + + val layer = GeoJsonLayer(mockMap, emptyFeatureCollection, markerManager, polygonManager, polylineManager, null) + layer.addLayerToMap() + + val customIcon = mockk() + val pointFeature = + GeoJsonFeature( + GeoJsonPoint(com.google.android.gms.maps.model.LatLng(37.7749, -122.4194)), + "pt_1", + HashMap(), + null, + ).apply { + pointStyle = + GeoJsonPointStyle().apply { + setTitle("Styled Title") + setSnippet("Styled Snippet") + setDraggable(true) + setFlat(true) + setVisible(false) + setIcon(customIcon) + } + } + layer.addFeature(pointFeature) + + org.junit.Assert.assertEquals("Styled Title", markerSlot.captured.title) + org.junit.Assert.assertEquals("Styled Snippet", markerSlot.captured.snippet) + assertTrue(markerSlot.captured.isDraggable) + assertTrue(markerSlot.captured.isFlat) + assertFalse(markerSlot.captured.isVisible) + org.junit.Assert.assertSame(customIcon, markerSlot.captured.icon) + + val multiLine = + GeoJsonMultiLineString( + listOf( + GeoJsonLineString( + listOf( + com.google.android.gms.maps.model.LatLng(37.0, -122.0), + com.google.android.gms.maps.model.LatLng(37.1, -122.1), + ), + ), + ), + ) + val multiLineFeature = + GeoJsonFeature(multiLine, "ml_1", HashMap(), null).apply { + lineStringStyle = + GeoJsonLineStringStyle().apply { + color = android.graphics.Color.RED + setWidth(15f) + } + } + layer.addFeature(multiLineFeature) + + assertTrue(polylineSlot.captured.isClickable) + org.junit.Assert.assertEquals(15f, polylineSlot.captured.width, 0.001f) + org.junit.Assert.assertEquals(android.graphics.Color.RED, polylineSlot.captured.color) + assertTrue(markerManager.remove(mockMarker)) + } finally { + io.mockk.unmockkStatic(com.google.android.gms.maps.model.BitmapDescriptorFactory::class) + } + } } diff --git a/data/src/test/java/com/google/maps/android/data/kml/KmlLayerOnMapTest.kt b/data/src/test/java/com/google/maps/android/data/kml/KmlLayerOnMapTest.kt index 1b462c2ac..61ae209f9 100644 --- a/data/src/test/java/com/google/maps/android/data/kml/KmlLayerOnMapTest.kt +++ b/data/src/test/java/com/google/maps/android/data/kml/KmlLayerOnMapTest.kt @@ -95,6 +95,70 @@ class KmlLayerOnMapTest { val layer = KmlLayer(mockk(relaxed = true), kml.byteInputStream(), context) layer.addLayerToMap() assertTrue(layer.isLayerOnMap()) + org.junit.Assert.assertFalse(layer.hasPlacemarks()) + org.junit.Assert.assertEquals(0, layer.getPlacemarks().toList().size) + org.junit.Assert.assertEquals(0, layer.features.toList().size) + org.junit.Assert.assertEquals(3, layer.getAllPlacemarks().size) + } finally { + unmockkStatic(BitmapDescriptorFactory::class) + } + } + + @Test + fun removeLayerFromMap_removesGroundOverlaysAndPreservesMultiGeometryStyles() { + mockkStatic(BitmapDescriptorFactory::class) + every { BitmapDescriptorFactory.defaultMarker(any()) } returns mockk() + every { BitmapDescriptorFactory.fromBitmap(any()) } returns mockk() + try { + val mockMap = mockk(relaxed = true) + val mockGroundOverlay = mockk(relaxed = true) + val polylineSlot = io.mockk.slot() + every { mockMap.addGroundOverlay(any()) } returns mockGroundOverlay + every { mockMap.addPolyline(capture(polylineSlot)) } returns mockk(relaxed = true) + + val kml = + """ + + + + + + + + -122.084,37.422,0 -122.080,37.425,0 + + + + + Campus Map + https://example.com/overlay.png + + 37.43 + 37.41 + -122.07 + -122.09 + + + + + """.trimIndent() + val context = ApplicationProvider.getApplicationContext() + val layer = KmlLayer(mockMap, kml.byteInputStream(), context) + layer.addLayerToMap() + + org.junit.Assert.assertEquals(0, layer.getGroundOverlays().toList().size) + org.junit.Assert.assertEquals(1, layer.getAllGroundOverlays().size) + assertTrue(polylineSlot.captured.isClickable) + org.junit.Assert.assertEquals(12.0f, polylineSlot.captured.width, 0.001f) + org.junit.Assert.assertEquals(0xFFFF0000.toInt(), polylineSlot.captured.color) + + layer.removeLayerFromMap() + io.mockk.verify(exactly = 1) { mockGroundOverlay.remove() } } finally { unmockkStatic(BitmapDescriptorFactory::class) } diff --git a/heatmaps/src/main/java/com/google/maps/android/heatmaps/HeatmapTileProvider.kt b/heatmaps/src/main/java/com/google/maps/android/heatmaps/HeatmapTileProvider.kt index d643e2bc6..11a4a4816 100644 --- a/heatmaps/src/main/java/com/google/maps/android/heatmaps/HeatmapTileProvider.kt +++ b/heatmaps/src/main/java/com/google/maps/android/heatmaps/HeatmapTileProvider.kt @@ -201,12 +201,14 @@ class HeatmapTileProvider private constructor( } fun setRadius(radius: Int) { + require(radius in MIN_RADIUS..MAX_RADIUS) { "Radius not within bounds." } this.radius = radius this.kernel = generateKernel(this.radius, this.radius / 3.0) this.maxIntensity = getMaxIntensities(this.radius) } fun setOpacity(opacity: Double) { + require(opacity in 0.0..1.0) { "Opacity must be in range [0, 1]" } this.opacity = opacity setGradient(this.gradient) } @@ -224,7 +226,8 @@ class HeatmapTileProvider private constructor( val tileWidth = WORLD_WIDTH / 2.0.pow(zoom.toDouble()) val padding = tileWidth * radius / TILE_DIM val tileWidthPadded = tileWidth + 2 * padding - val bucketWidth = tileWidthPadded / (TILE_DIM + radius * 2) + val gridDim = TILE_DIM + radius * 2 + val bucketWidth = tileWidthPadded / gridDim val minX = x * tileWidth - padding val maxX = (x + 1) * tileWidth + padding val minY = y * tileWidth - padding @@ -251,31 +254,31 @@ class HeatmapTileProvider private constructor( bounds.minY - padding, bounds.maxY + padding, ) - if (!tileBounds.intersects(paddedBounds)) { + if (!tileBounds.intersects(paddedBounds) && wrappedPoints.isEmpty()) { return TileProvider.NO_TILE } val points = tree.search(tileBounds) - if (points.isEmpty()) { + if (points.isEmpty() && wrappedPoints.isEmpty()) { return TileProvider.NO_TILE } - val intensity = Array(TILE_DIM + radius * 2) { DoubleArray(TILE_DIM + radius * 2) } + val intensity = Array(gridDim) { DoubleArray(gridDim) } for (w in points) { val p = w.point - val bucketX = ((p.x - minX) / bucketWidth).toInt() - val bucketY = ((p.y - minY) / bucketWidth).toInt() + val bucketX = ((p.x - minX) / bucketWidth).toInt().coerceIn(0, gridDim - 1) + val bucketY = ((p.y - minY) / bucketWidth).toInt().coerceIn(0, gridDim - 1) intensity[bucketX][bucketY] += w.intensity } for (w in wrappedPoints) { val p = w.point - val bucketX = ((p.x + xOffset - minX) / bucketWidth).toInt() - val bucketY = ((p.y - minY) / bucketWidth).toInt() + val bucketX = ((p.x + xOffset - minX) / bucketWidth).toInt().coerceIn(0, gridDim - 1) + val bucketY = ((p.y - minY) / bucketWidth).toInt().coerceIn(0, gridDim - 1) intensity[bucketX][bucketY] += w.intensity } val convolved = convolve(intensity, kernel) - val bitmap = colorize(convolved, colorMap, maxIntensity[zoom]) + val bitmap = colorize(convolved, colorMap, maxIntensity[zoom.coerceIn(0, maxIntensity.lastIndex)]) return convertBitmap(bitmap) } diff --git a/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapTileProviderTest.kt b/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapTileProviderTest.kt index 2d73cdec9..273a47bc8 100644 --- a/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapTileProviderTest.kt +++ b/heatmaps/src/test/java/com/google/maps/android/heatmaps/HeatmapTileProviderTest.kt @@ -137,4 +137,67 @@ class HeatmapTileProviderTest { val tile = provider.getTile(1, 1, 20) assertThat(tile).isEqualTo(TileProvider.NO_TILE) } + + @Test + fun testGetTile_zoomLevel22DoesNotThrowOutOfBounds() { + val data = listOf(LatLng(0.0, 0.0)) + val provider = HeatmapTileProvider.Builder().data(data).build() + val numTiles = 1 shl 22 + val centerTile = numTiles / 2 + val tile = provider.getTile(centerTile, centerTile, 22) + assertThat(tile).isNotNull() + assertThat(tile).isNotEqualTo(TileProvider.NO_TILE) + } + + @Test + fun testGetTile_upperBoundaryPointDoesNotThrowOutOfBounds() { + val data = listOf(LatLng(0.0, 0.0), LatLng(10.0, 10.0)) + val provider = HeatmapTileProvider.Builder().data(data).radius(20).build() + val zoom = 2 + val numTiles = 1 shl zoom + val padding = (1.0 / numTiles) * 20 / 512.0 + val exactMaxX = 2 * (1.0 / numTiles) + padding + val exactLng = (exactMaxX - 0.5) * 360.0 + provider.updateLatLngs(listOf(LatLng(0.0, 0.0), LatLng(0.0, exactLng))) + val tile = provider.getTile(1, 1, zoom) + assertThat(tile).isNotNull() + } + + @Test + fun testGetTile_antimeridianWrappedPointsAreRendered() { + val pointNearEastDateLine = LatLng(0.0, 179.99) + val provider = + HeatmapTileProvider + .Builder() + .data(listOf(pointNearEastDateLine)) + .radius(50) + .build() + val tile = provider.getTile(0, 1, 1) + assertThat(tile).isNotEqualTo(TileProvider.NO_TILE) + } + + @Test + fun testSetRadiusAndSetOpacity_validateBounds() { + val provider = HeatmapTileProvider.Builder().data(listOf(LatLng(0.0, 0.0))).build() + try { + provider.setRadius(0) + fail("Should have thrown IllegalArgumentException") + } catch (_: IllegalArgumentException) { + } + try { + provider.setRadius(100) + fail("Should have thrown IllegalArgumentException") + } catch (_: IllegalArgumentException) { + } + try { + provider.setOpacity(-0.5) + fail("Should have thrown IllegalArgumentException") + } catch (_: IllegalArgumentException) { + } + try { + provider.setOpacity(1.5) + fail("Should have thrown IllegalArgumentException") + } catch (_: IllegalArgumentException) { + } + } } diff --git a/library/src/main/java/com/google/maps/android/collections/MapObjectManager.kt b/library/src/main/java/com/google/maps/android/collections/MapObjectManager.kt index 5c6b7116a..acca571e1 100644 --- a/library/src/main/java/com/google/maps/android/collections/MapObjectManager.kt +++ b/library/src/main/java/com/google/maps/android/collections/MapObjectManager.kt @@ -39,8 +39,13 @@ abstract class MapObjectManager.Collection>( protected val mAllObjects: MutableMap = mutableMapOf() init { - Handler(Looper.getMainLooper()).post { + val mainLooper = Looper.getMainLooper() + if (mainLooper == null || Looper.myLooper() == mainLooper) { setListenersOnUiThread() + } else { + Handler(mainLooper).post { + setListenersOnUiThread() + } } } diff --git a/library/src/test/java/com/google/maps/android/collections/MapObjectManagerTest.kt b/library/src/test/java/com/google/maps/android/collections/MapObjectManagerTest.kt index bb1e2f54d..be2d93ab5 100644 --- a/library/src/test/java/com/google/maps/android/collections/MapObjectManagerTest.kt +++ b/library/src/test/java/com/google/maps/android/collections/MapObjectManagerTest.kt @@ -154,4 +154,26 @@ class MapObjectManagerTest { assertThat(obj.isVisible).isFalse() } } + + @Test + fun testSynchronousListenerRegistrationOnUiThread() { + var listenersRegistered = false + val syncManager = + object : MapObjectManager(map) { + override fun setListenersOnUiThread() { + listenersRegistered = true + } + + override fun newCollection(): ConcreteManager.Collection = manager.newCollection() + + override fun removeObjectFromMap(mapObject: TestObject) {} + + override fun setVisible( + mapObject: TestObject, + visible: Boolean, + ) {} + } + assertThat(syncManager).isNotNull() + assertThat(listenersRegistered).isTrue() + } } diff --git a/ui/src/main/java/com/google/maps/android/ui/AnimationUtil.kt b/ui/src/main/java/com/google/maps/android/ui/AnimationUtil.kt index 0b8f95d6f..fe7ab58e7 100644 --- a/ui/src/main/java/com/google/maps/android/ui/AnimationUtil.kt +++ b/ui/src/main/java/com/google/maps/android/ui/AnimationUtil.kt @@ -42,6 +42,10 @@ object AnimationUtil { finalPosition: LatLng, durationInMs: Long = 2000, ) { + if (durationInMs <= 0L) { + marker.position = finalPosition + return + } val latLngInterpolator = LatLngInterpolator.Linear() val startPosition = marker.position val handler = Handler(Looper.getMainLooper()) @@ -52,12 +56,13 @@ object AnimationUtil { object : Runnable { override fun run() { val elapsed = SystemClock.uptimeMillis() - start - val t = elapsed / durationInMs.toFloat() + val rawT = elapsed / durationInMs.toFloat() + val t = rawT.coerceIn(0f, 1f) val v = interpolator.getInterpolation(t) marker.position = latLngInterpolator.interpolate(v, startPosition, finalPosition) // Repeat till progress is complete. - if (t < 1) { + if (rawT < 1f) { // Post again 16ms later. handler.postDelayed(this, 16) } diff --git a/ui/src/main/java/com/google/maps/android/ui/IconGenerator.kt b/ui/src/main/java/com/google/maps/android/ui/IconGenerator.kt index 716a67171..fabd8494e 100644 --- a/ui/src/main/java/com/google/maps/android/ui/IconGenerator.kt +++ b/ui/src/main/java/com/google/maps/android/ui/IconGenerator.kt @@ -133,7 +133,7 @@ class IconGenerator(private val context: Context) { * @param degrees the amount the icon should be rotated, as a multiple of 90 degrees. */ fun setRotation(degrees: Int) { - rotation = (degrees + 360) % 360 / 90 + rotation = degrees.mod(360) / 90 } /** diff --git a/ui/src/main/java/com/google/maps/android/ui/RotationLayout.kt b/ui/src/main/java/com/google/maps/android/ui/RotationLayout.kt index bbe0e224a..9169c7263 100644 --- a/ui/src/main/java/com/google/maps/android/ui/RotationLayout.kt +++ b/ui/src/main/java/com/google/maps/android/ui/RotationLayout.kt @@ -50,7 +50,7 @@ class RotationLayout * @param degrees the rotation, in degrees. */ fun setViewRotation(degrees: Int) { - rotation = (degrees + 360) % 360 / 90 + rotation = degrees.mod(360) / 90 } public override fun dispatchDraw(canvas: Canvas) { diff --git a/ui/src/test/java/com/google/maps/android/AnimationUtilTest.kt b/ui/src/test/java/com/google/maps/android/AnimationUtilTest.kt index 057daae1e..057643746 100644 --- a/ui/src/test/java/com/google/maps/android/AnimationUtilTest.kt +++ b/ui/src/test/java/com/google/maps/android/AnimationUtilTest.kt @@ -74,4 +74,22 @@ class AnimationUtilTest { assertEquals(10.0, currentPosition.latitude, 0.5) // 0.5 tolerance assertEquals(10.0, currentPosition.longitude, 0.5) // 0.5 tolerance } + + @Test + fun `animateMarkerTo with zero duration snaps directly to final position without NaN`() { + val finalPosition = LatLng(37.7749, -122.4194) + AnimationUtil.animateMarkerTo(marker, finalPosition, 0L) + assertEquals(37.7749, currentPosition.latitude, 1e-6) + assertEquals(-122.4194, currentPosition.longitude, 1e-6) + } + + @Test + fun `animateMarkerTo clamps elapsed time overshoot to final position`() { + val finalPosition = LatLng(10.0, 20.0) + AnimationUtil.animateMarkerTo(marker, finalPosition, 100L) + val mainLooper = Shadows.shadowOf(android.os.Looper.getMainLooper()) + mainLooper.idleFor(160, TimeUnit.MILLISECONDS) + assertEquals(10.0, currentPosition.latitude, 1e-6) + assertEquals(20.0, currentPosition.longitude, 1e-6) + } } diff --git a/ui/src/test/java/com/google/maps/android/ui/IconGeneratorTest.kt b/ui/src/test/java/com/google/maps/android/ui/IconGeneratorTest.kt index dbcc82a11..5ff8c50d6 100644 --- a/ui/src/test/java/com/google/maps/android/ui/IconGeneratorTest.kt +++ b/ui/src/test/java/com/google/maps/android/ui/IconGeneratorTest.kt @@ -305,4 +305,14 @@ class IconGeneratorTest { val bitmap = iconGenerator.makeIcon("Color Test") assertNotNull(bitmap) } + + @Test + fun testNegativeMultiTurnRotation() { + iconGenerator.setRotation(-450) + iconGenerator.setContentRotation(-450) + assertEquals(1.0f, iconGenerator.getAnchorU(), 0.001f) + assertEquals(0.5f, iconGenerator.getAnchorV(), 0.001f) + val bitmap = iconGenerator.makeIcon("Negative Rotation") + assertNotNull(bitmap) + } } From e8be5afac17d41e63578cf40b33c490cb3aea0c5 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:53:46 -0600 Subject: [PATCH 17/17] chore: enable Kotlin explicitApi() and Binary Compatibility Validator (.api dumps) (#1800) * chore: enable Kotlin explicitApi() and Binary Compatibility Validator (.api dumps) - Enable kotlin explicitApi() across published modules (:library, :clustering, :data, :heatmaps, :ui) - Add explicit public visibility and return types to all public Kotlin symbols across modules - Configure Binary Compatibility Validator (BCV) for AGP 9.3 via build-logic convention plugin and root aggregate tasks - Generate and commit baseline .api dumps for :library, :clustering, :data, :heatmaps, and :ui - Add apiCheck to the CI Pull Request test workflow Fixes #1794 * fix: restore public visibility on ResponseStreetView and update library.api --- .github/workflows/test.yml | 2 +- build-logic/convention/build.gradle.kts | 1 + .../main/kotlin/PublishingConventionPlugin.kt | 65 + build.gradle.kts | 15 + clustering/api/clustering.api | 449 +++ clustering/build.gradle.kts | 1 + .../google/maps/android/clustering/Cluster.kt | 8 +- .../maps/android/clustering/ClusterItem.kt | 10 +- .../maps/android/clustering/ClusterManager.kt | 78 +- .../clustering/algo/AbstractAlgorithm.kt | 2 +- .../maps/android/clustering/algo/Algorithm.kt | 24 +- ...idNonHierarchicalDistanceBasedAlgorithm.kt | 2 +- ...ontinuousZoomEuclideanCentroidAlgorithm.kt | 2 +- .../clustering/algo/GridBasedAlgorithm.kt | 4 +- .../NonHierarchicalDistanceBasedAlgorithm.kt | 8 +- .../algo/NonHierarchicalViewBasedAlgorithm.kt | 8 +- .../algo/PreCachingAlgorithmDecorator.kt | 2 +- .../clustering/algo/ScreenBasedAlgorithm.kt | 6 +- .../algo/ScreenBasedAlgorithmAdapter.kt | 2 +- .../android/clustering/algo/StaticCluster.kt | 6 +- .../clustering/view/ClusterRenderer.kt | 28 +- .../view/ClusterRendererMultipleItems.kt | 24 +- .../DefaultAdvancedMarkersClusterRenderer.kt | 14 +- .../clustering/view/DefaultClusterRenderer.kt | 14 +- .../google/maps/android/geometry/Bounds.kt | 24 +- .../com/google/maps/android/geometry/Point.kt | 8 +- .../projection/SphericalMercatorProjection.kt | 6 +- .../maps/android/quadtree/PointQuadTree.kt | 20 +- data/api/data.api | 2518 +++++++++++++++++ data/build.gradle.kts | 1 + .../maps/android/data/DataLayerLoader.kt | 6 +- .../com/google/maps/android/data/Layer.kt | 6 +- .../com/google/maps/android/data/Style.kt | 2 +- .../data/geojson/GeoJsonLineStringStyle.kt | 4 +- .../android/data/geojson/GeoJsonPointStyle.kt | 4 +- .../android/data/geojson/GeoJsonPolygon.kt | 12 +- .../data/geojson/GeoJsonPolygonStyle.kt | 4 +- .../maps/android/data/kml/KmlPolygon.kt | 14 +- .../google/maps/android/data/kml/KmlStyle.kt | 4 +- .../maps/android/data/kml/KmlUrlSanitizer.kt | 4 +- .../maps/android/data/parser/GeoFileParser.kt | 2 +- .../google/maps/android/data/parser/Model.kt | 44 +- .../data/parser/geojson/GeoJsonObjects.kt | 66 +- .../data/parser/geojson/GeoJsonParser.kt | 16 +- .../maps/android/data/parser/gpx/GpxModel.kt | 60 +- .../maps/android/data/parser/gpx/GpxParser.kt | 10 +- .../maps/android/data/parser/kml/KmlModel.kt | 244 +- .../maps/android/data/parser/kml/KmlParser.kt | 14 +- .../maps/android/data/parser/kml/KmzParser.kt | 18 +- .../maps/android/data/parser/kml/LatLngAlt.kt | 8 +- .../android/data/renderer/DataRenderer.kt | 14 +- .../android/data/renderer/IconProvider.kt | 4 +- .../android/data/renderer/UrlIconProvider.kt | 2 +- .../data/renderer/mapper/GeoJsonMapper.kt | 14 +- .../android/data/renderer/mapper/GpxMapper.kt | 6 +- .../android/data/renderer/mapper/KmlMapper.kt | 6 +- .../data/renderer/mapview/MapViewRenderer.kt | 12 +- .../android/data/renderer/model/DataLayer.kt | 8 +- .../android/data/renderer/model/DataScene.kt | 12 +- .../android/data/renderer/model/Feature.kt | 8 +- .../android/data/renderer/model/Geometry.kt | 30 +- .../data/renderer/model/GroundOverlay.kt | 16 +- .../data/renderer/model/GroundOverlayStyle.kt | 10 +- .../maps/android/data/renderer/model/Point.kt | 8 +- .../android/renderer/GoogleMapRenderer.kt | 10 +- .../google/maps/android/renderer/Renderer.kt | 10 +- .../maps/android/renderer/model/Circle.kt | 22 +- .../maps/android/renderer/model/Layer.kt | 12 +- .../maps/android/renderer/model/MapObject.kt | 10 +- .../maps/android/renderer/model/Marker.kt | 28 +- .../maps/android/renderer/model/Polygon.kt | 28 +- .../maps/android/renderer/model/Polyline.kt | 26 +- gradle/libs.versions.toml | 3 + heatmaps/api/heatmaps.api | 98 + heatmaps/build.gradle.kts | 1 + .../google/maps/android/heatmaps/Gradient.kt | 16 +- .../android/heatmaps/HeatmapTileProvider.kt | 54 +- .../maps/android/heatmaps/WeightedLatLng.kt | 12 +- library/api/library.api | 768 +++++ library/build.gradle.kts | 1 + .../google/maps/android/FlexiblePolyline.kt | 40 +- .../java/com/google/maps/android/MathUtil.kt | 26 +- .../java/com/google/maps/android/PolyUtil.kt | 26 +- .../com/google/maps/android/SphericalUtil.kt | 26 +- .../maps/android/StreetViewJavaHelper.kt | 10 +- .../com/google/maps/android/StreetViewUtil.kt | 16 +- .../maps/android/collections/CircleManager.kt | 22 +- .../collections/GroundOverlayManager.kt | 22 +- .../android/collections/MapObjectManager.kt | 20 +- .../maps/android/collections/MarkerManager.kt | 46 +- .../android/collections/PolygonManager.kt | 22 +- .../android/collections/PolylineManager.kt | 22 +- ui/api/ui.api | 86 + ui/build.gradle.kts | 1 + .../google/maps/android/ui/AnimationUtil.kt | 12 +- .../google/maps/android/ui/IconGenerator.kt | 44 +- .../google/maps/android/ui/RotationLayout.kt | 6 +- .../google/maps/android/ui/SquareTextView.kt | 8 +- 98 files changed, 4800 insertions(+), 798 deletions(-) create mode 100644 clustering/api/clustering.api create mode 100644 data/api/data.api create mode 100644 heatmaps/api/heatmaps.api create mode 100644 library/api/library.api create mode 100644 ui/api/ui.api diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64c2afe6b..3cef29b74 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,4 +46,4 @@ jobs: run: echo "MAPS_API_KEY=dummy" > secrets.properties - name: Build modules - run: ./gradlew build koverXmlReportDebug --stacktrace + run: ./gradlew build apiCheck koverXmlReportDebug --stacktrace diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 244d1adc9..0ae62850f 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(libs.dokka.gradle.plugin) implementation(libs.kover.gradle.plugin) implementation(libs.gradle.maven.publish.plugin) + implementation(libs.binary.compatibility.validator) } gradlePlugin { diff --git a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt index 7b681a2be..ebf8318ea 100644 --- a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt @@ -18,8 +18,11 @@ import com.vanniktech.maven.publish.AndroidSingleVariantLibrary import com.vanniktech.maven.publish.MavenPublishBaseExtension import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension +import kotlinx.validation.KotlinApiBuildTask +import kotlinx.validation.KotlinApiCompareTask import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.tasks.Copy import org.gradle.kotlin.dsl.* class PublishingConventionPlugin : Plugin { @@ -28,6 +31,7 @@ class PublishingConventionPlugin : Plugin { applyPlugins() configureKover() configureVanniktechPublishing() + configureBinaryCompatibilityValidator() } } @@ -106,4 +110,65 @@ class PublishingConventionPlugin : Plugin { } } } + + private fun Project.configureBinaryCompatibilityValidator() { + val ignoredProjects = setOf("demo", "visual-testing", "lint-checks", "maps-utils") + if (name in ignoredProjects) return + + val projectName = name + val apiFile = layout.projectDirectory.file("api/$projectName.api") + val buildApiFile = layout.buildDirectory.file("api/$projectName.api") + + afterEvaluate { + val bundleTask = tasks.findByName("bundleLibCompileToJarRelease") ?: return@afterEvaluate + val classesJar = layout.buildDirectory.file( + "intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar" + ) + + val apiBuild = tasks.register("apiBuild") { + group = "verification" + description = "Builds public API declaration for $projectName." + inputJar.set(classesJar) + outputApiFile.set(buildApiFile) + ignoredClasses.addAll( + "com.google.maps.android.R", + "com.google.maps.android.clustering.R", + "com.google.maps.android.data.R", + "com.google.maps.android.heatmaps.R", + "com.google.maps.android.ui.R", + "com.google.maps.android.BuildConfig", + "com.google.maps.android.clustering.BuildConfig", + "com.google.maps.android.data.BuildConfig", + "com.google.maps.android.heatmaps.BuildConfig", + "com.google.maps.android.ui.BuildConfig", + ) + dependsOn(bundleTask) + } + + val apiDump = tasks.register("apiDump") { + group = "verification" + description = "Syncs public API declarations of $projectName to the project api/ directory." + from(apiBuild.flatMap { it.outputApiFile }) + into(apiFile.asFile.parentFile) + dependsOn(apiBuild) + } + + val apiCheck = tasks.register("apiCheck") { + group = "verification" + description = "Checks public API declarations of $projectName against the committed api/$projectName.api." + projectApiFile.set(apiFile) + generatedApiFile.set(apiBuild.flatMap { it.outputApiFile }) + dependsOn(apiBuild) + } + + tasks.findByName("check")?.dependsOn(apiCheck) + + rootProject.tasks.matching { it.name == "apiDump" }.configureEach { + dependsOn(apiDump) + } + rootProject.tasks.matching { it.name == "apiCheck" }.configureEach { + dependsOn(apiCheck) + } + } + } } diff --git a/build.gradle.kts b/build.gradle.kts index feb04cf54..9c9b07388 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,21 @@ import java.util.Properties plugins { id("com.vanniktech.maven.publish") version libs.versions.gradleMavenPublishPlugin.get() apply false + alias(libs.plugins.binary.compatibility.validator) +} + +apiValidation { + ignoredProjects += listOf("demo", "visual-testing", "lint-checks", "maps-utils") +} + +val apiDump = tasks.register("apiDump") { + group = "verification" + description = "Dumps public API declarations for all library modules." +} + +val apiCheck = tasks.register("apiCheck") { + group = "verification" + description = "Checks that public API declarations match the committed .api files." } buildscript { diff --git a/clustering/api/clustering.api b/clustering/api/clustering.api new file mode 100644 index 000000000..a6a39422e --- /dev/null +++ b/clustering/api/clustering.api @@ -0,0 +1,449 @@ +public abstract interface class com/google/maps/android/clustering/Cluster { + public abstract fun getItems ()Ljava/util/Collection; + public abstract fun getPosition ()Lcom/google/android/gms/maps/model/LatLng; + public abstract fun getSize ()I +} + +public abstract interface class com/google/maps/android/clustering/ClusterItem { + public abstract fun getPosition ()Lcom/google/android/gms/maps/model/LatLng; + public abstract fun getSnippet ()Ljava/lang/String; + public abstract fun getTitle ()Ljava/lang/String; + public abstract fun getZIndex ()Ljava/lang/Float; +} + +public class com/google/maps/android/clustering/ClusterManager : com/google/android/gms/maps/GoogleMap$OnCameraIdleListener, com/google/android/gms/maps/GoogleMap$OnInfoWindowClickListener, com/google/android/gms/maps/GoogleMap$OnMarkerClickListener { + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;)V + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/collections/MarkerManager;)V + public synthetic fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/collections/MarkerManager;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun addItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun addItems (Ljava/util/Collection;)Z + public fun clearItems ()V + public fun cluster ()V + public fun diff (Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;)V + public fun getAlgorithm ()Lcom/google/maps/android/clustering/algo/Algorithm; + public final fun getClusterMarkerCollection ()Lcom/google/maps/android/collections/MarkerManager$Collection; + public final fun getMarkerCollection ()Lcom/google/maps/android/collections/MarkerManager$Collection; + public final fun getMarkerManager ()Lcom/google/maps/android/collections/MarkerManager; + public fun getRenderer ()Lcom/google/maps/android/clustering/view/ClusterRenderer; + public fun onCameraIdle ()V + public fun onInfoWindowClick (Lcom/google/android/gms/maps/model/Marker;)V + public fun onMarkerClick (Lcom/google/android/gms/maps/model/Marker;)Z + public fun removeItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun removeItems (Ljava/util/Collection;)Z + public fun setAlgorithm (Lcom/google/maps/android/clustering/algo/Algorithm;)V + public fun setAlgorithm (Lcom/google/maps/android/clustering/algo/ScreenBasedAlgorithm;)V + public fun setAnimation (Z)V + public fun setOnClusterClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterClickListener;)V + public fun setOnClusterInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowClickListener;)V + public fun setOnClusterInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowLongClickListener;)V + public fun setOnClusterItemClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemClickListener;)V + public fun setOnClusterItemInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowClickListener;)V + public fun setOnClusterItemInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowLongClickListener;)V + public fun setRenderer (Lcom/google/maps/android/clustering/view/ClusterRenderer;)V + public fun updateItem (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public abstract interface class com/google/maps/android/clustering/ClusterManager$OnClusterClickListener { + public abstract fun onClusterClick (Lcom/google/maps/android/clustering/Cluster;)Z +} + +public abstract interface class com/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowClickListener { + public abstract fun onClusterInfoWindowClick (Lcom/google/maps/android/clustering/Cluster;)V +} + +public abstract interface class com/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowLongClickListener { + public abstract fun onClusterInfoWindowLongClick (Lcom/google/maps/android/clustering/Cluster;)V +} + +public abstract interface class com/google/maps/android/clustering/ClusterManager$OnClusterItemClickListener { + public abstract fun onClusterItemClick (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public abstract interface class com/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowClickListener { + public abstract fun onClusterItemInfoWindowClick (Lcom/google/maps/android/clustering/ClusterItem;)V +} + +public abstract interface class com/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowLongClickListener { + public abstract fun onClusterItemInfoWindowLongClick (Lcom/google/maps/android/clustering/ClusterItem;)V +} + +public final class com/google/maps/android/clustering/ClusterManagerFlowsKt { + public static final fun clusterClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterInfoWindowClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterInfoWindowLongClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterItemClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterItemInfoWindowClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterItemInfoWindowLongClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; +} + +public abstract class com/google/maps/android/clustering/algo/AbstractAlgorithm : com/google/maps/android/clustering/algo/Algorithm { + public fun ()V + public fun lock ()V + public fun unlock ()V +} + +public abstract interface class com/google/maps/android/clustering/algo/Algorithm { + public abstract fun addItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public abstract fun addItems (Ljava/util/Collection;)Z + public abstract fun clearItems ()V + public abstract fun getClusters (F)Ljava/util/Set; + public abstract fun getItems ()Ljava/util/Collection; + public abstract fun getMaxDistanceBetweenClusteredItems ()I + public abstract fun lock ()V + public abstract fun removeItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public abstract fun removeItems (Ljava/util/Collection;)Z + public abstract fun setMaxDistanceBetweenClusteredItems (I)V + public abstract fun unlock ()V + public abstract fun updateItem (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public class com/google/maps/android/clustering/algo/CentroidNonHierarchicalDistanceBasedAlgorithm : com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm { + public fun ()V + protected final fun computeCentroid (Ljava/util/Collection;)Lcom/google/android/gms/maps/model/LatLng; + public fun getClusters (F)Ljava/util/Set; +} + +public class com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm : com/google/maps/android/clustering/algo/CentroidNonHierarchicalDistanceBasedAlgorithm { + public fun ()V + public fun getClusters (F)Ljava/util/Set; +} + +public class com/google/maps/android/clustering/algo/GridBasedAlgorithm : com/google/maps/android/clustering/algo/AbstractAlgorithm { + public static final field Companion Lcom/google/maps/android/clustering/algo/GridBasedAlgorithm$Companion; + public fun ()V + public fun addItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun addItems (Ljava/util/Collection;)Z + public fun clearItems ()V + public fun getClusters (F)Ljava/util/Set; + public fun getItems ()Ljava/util/Collection; + public fun getMaxDistanceBetweenClusteredItems ()I + public fun removeItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun removeItems (Ljava/util/Collection;)Z + public fun setMaxDistanceBetweenClusteredItems (I)V + public fun updateItem (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public final class com/google/maps/android/clustering/algo/GridBasedAlgorithm$Companion { +} + +public class com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm : com/google/maps/android/clustering/algo/AbstractAlgorithm { + public static final field Companion Lcom/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm$Companion; + protected final field mItems Ljava/util/Collection; + protected final field mQuadTree Lcom/google/maps/android/quadtree/PointQuadTree; + public fun ()V + public fun addItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun addItems (Ljava/util/Collection;)Z + public fun clearItems ()V + protected final fun createBoundsFromSpan (Lcom/google/maps/android/geometry/Point;D)Lcom/google/maps/android/geometry/Bounds; + protected final fun distanceSquared (Lcom/google/maps/android/geometry/Point;Lcom/google/maps/android/geometry/Point;)D + protected fun getClusteringItems (Lcom/google/maps/android/quadtree/PointQuadTree;F)Ljava/util/Collection; + public fun getClusters (F)Ljava/util/Set; + public fun getItems ()Ljava/util/Collection; + protected final fun getMItemMap ()Ljava/util/Map; + public fun getMaxDistanceBetweenClusteredItems ()I + public fun removeItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun removeItems (Ljava/util/Collection;)Z + public fun setMaxDistanceBetweenClusteredItems (I)V + public fun updateItem (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public final class com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm$Companion { +} + +protected final class com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm$QuadItem : com/google/maps/android/clustering/Cluster, com/google/maps/android/quadtree/PointQuadTree$Item { + public final field mClusterItem Lcom/google/maps/android/clustering/ClusterItem; + public fun (Lcom/google/maps/android/clustering/ClusterItem;)V + public fun equals (Ljava/lang/Object;)Z + public fun getItems ()Ljava/util/Collection; + public fun getPoint ()Lcom/google/maps/android/geometry/Point; + public fun getPosition ()Lcom/google/android/gms/maps/model/LatLng; + public fun getSize ()I + public fun hashCode ()I +} + +public class com/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm : com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm, com/google/maps/android/clustering/algo/ScreenBasedAlgorithm { + public static final field Companion Lcom/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm$Companion; + public fun (II)V + protected fun getClusteringItems (Lcom/google/maps/android/quadtree/PointQuadTree;F)Ljava/util/Collection; + public fun onCameraChange (Lcom/google/android/gms/maps/model/CameraPosition;)V + public fun shouldReclusterOnMapMovement ()Z + public final fun updateViewSize (II)V +} + +public final class com/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm$Companion { +} + +public class com/google/maps/android/clustering/algo/PreCachingAlgorithmDecorator : com/google/maps/android/clustering/algo/AbstractAlgorithm { + public fun (Lcom/google/maps/android/clustering/algo/Algorithm;)V + public fun addItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun addItems (Ljava/util/Collection;)Z + public fun clearItems ()V + public fun getClusters (F)Ljava/util/Set; + public fun getItems ()Ljava/util/Collection; + public fun getMaxDistanceBetweenClusteredItems ()I + public fun removeItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun removeItems (Ljava/util/Collection;)Z + public fun setMaxDistanceBetweenClusteredItems (I)V + public fun updateItem (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public abstract interface class com/google/maps/android/clustering/algo/ScreenBasedAlgorithm : com/google/maps/android/clustering/algo/Algorithm { + public abstract fun onCameraChange (Lcom/google/android/gms/maps/model/CameraPosition;)V + public abstract fun shouldReclusterOnMapMovement ()Z +} + +public class com/google/maps/android/clustering/algo/ScreenBasedAlgorithmAdapter : com/google/maps/android/clustering/algo/AbstractAlgorithm, com/google/maps/android/clustering/algo/ScreenBasedAlgorithm { + public fun (Lcom/google/maps/android/clustering/algo/Algorithm;)V + public fun addItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun addItems (Ljava/util/Collection;)Z + public fun clearItems ()V + public fun getClusters (F)Ljava/util/Set; + public fun getItems ()Ljava/util/Collection; + public fun getMaxDistanceBetweenClusteredItems ()I + public fun onCameraChange (Lcom/google/android/gms/maps/model/CameraPosition;)V + public fun removeItem (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun removeItems (Ljava/util/Collection;)Z + public fun setMaxDistanceBetweenClusteredItems (I)V + public fun shouldReclusterOnMapMovement ()Z + public fun updateItem (Lcom/google/maps/android/clustering/ClusterItem;)Z +} + +public class com/google/maps/android/clustering/algo/StaticCluster : com/google/maps/android/clustering/Cluster { + public fun (Lcom/google/android/gms/maps/model/LatLng;)V + public final fun add (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun equals (Ljava/lang/Object;)Z + public fun getItems ()Ljava/util/Collection; + public fun getPosition ()Lcom/google/android/gms/maps/model/LatLng; + public fun getSize ()I + public fun hashCode ()I + public final fun remove (Lcom/google/maps/android/clustering/ClusterItem;)Z + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/clustering/view/ClusterRenderer { + public abstract fun getClusterTextAppearance (I)I + public abstract fun getColor (I)I + public abstract fun onAdd ()V + public abstract fun onClustersChanged (Ljava/util/Set;)V + public abstract fun onRemove ()V + public abstract fun setAnimation (Z)V + public abstract fun setAnimationDuration (J)V + public abstract fun setOnClusterClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterClickListener;)V + public abstract fun setOnClusterInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowClickListener;)V + public abstract fun setOnClusterInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowLongClickListener;)V + public abstract fun setOnClusterItemClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemClickListener;)V + public abstract fun setOnClusterItemInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowClickListener;)V + public abstract fun setOnClusterItemInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowLongClickListener;)V +} + +public class com/google/maps/android/clustering/view/ClusterRendererMultipleItems : com/google/maps/android/clustering/view/ClusterRenderer { + public static final field Companion Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$Companion; + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;)V + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;Ljava/util/concurrent/Executor;)V + public synthetic fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;Ljava/util/concurrent/Executor;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + protected final fun getBucket (Lcom/google/maps/android/clustering/Cluster;)I + public fun getCluster (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/clustering/Cluster; + public fun getClusterItem (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/clustering/ClusterItem; + protected final fun getClusterText (I)Ljava/lang/String; + public fun getClusterTextAppearance (I)I + public fun getColor (I)I + protected final fun getDescriptorForCluster (Lcom/google/maps/android/clustering/Cluster;)Lcom/google/android/gms/maps/model/BitmapDescriptor; + public fun getMarker (Lcom/google/maps/android/clustering/Cluster;)Lcom/google/android/gms/maps/model/Marker; + public fun getMarker (Lcom/google/maps/android/clustering/ClusterItem;)Lcom/google/android/gms/maps/model/Marker; + public fun getMinClusterSize ()I + public fun onAdd ()V + protected fun onBeforeClusterItemRendered (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/MarkerOptions;)V + protected fun onBeforeClusterRendered (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/MarkerOptions;)V + protected final fun onClusterItemRendered (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterItemUpdated (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/Marker;)V + protected final fun onClusterRendered (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterUpdated (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/Marker;)V + public fun onClustersChanged (Ljava/util/Set;)V + public fun onRemove ()V + public fun setAnimation (Z)V + public fun setAnimationDuration (J)V + public final fun setAnimationInterpolator (Landroid/animation/TimeInterpolator;)V + public final fun setAnimationType (Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType;)V + public final fun setLoggingEnabled (Z)V + public fun setMinClusterSize (I)V + public fun setOnClusterClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterClickListener;)V + public fun setOnClusterInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowClickListener;)V + public fun setOnClusterInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowLongClickListener;)V + public fun setOnClusterItemClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemClickListener;)V + public fun setOnClusterItemInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowClickListener;)V + public fun setOnClusterItemInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowLongClickListener;)V + protected fun shouldRenderAsCluster (Lcom/google/maps/android/clustering/Cluster;)Z + public final fun stopAnimation ()V +} + +public final class com/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType : java/lang/Enum { + public static final field ACCELERATE Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field BOUNCE Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field DECELERATE Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field EASE_IN Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field EASE_IN_OUT Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field EASE_OUT Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field FAST_OUT_SLOW_IN Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static final field LINEAR Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; + public static fun values ()[Lcom/google/maps/android/clustering/view/ClusterRendererMultipleItems$AnimationType; +} + +public final class com/google/maps/android/clustering/view/ClusterRendererMultipleItems$Companion { +} + +public class com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer : com/google/maps/android/clustering/view/ClusterRenderer { + public static final field Companion Lcom/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer$Companion; + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;)V + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;Ljava/util/concurrent/Executor;)V + public synthetic fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;Ljava/util/concurrent/Executor;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + protected fun getBucket (Lcom/google/maps/android/clustering/Cluster;)I + public fun getCluster (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/clustering/Cluster; + public fun getClusterItem (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/clustering/ClusterItem; + protected fun getClusterText (I)Ljava/lang/String; + public fun getClusterTextAppearance (I)I + public fun getColor (I)I + protected fun getDescriptorForCluster (Lcom/google/maps/android/clustering/Cluster;)Lcom/google/android/gms/maps/model/BitmapDescriptor; + public fun getMarker (Lcom/google/maps/android/clustering/Cluster;)Lcom/google/android/gms/maps/model/Marker; + public fun getMarker (Lcom/google/maps/android/clustering/ClusterItem;)Lcom/google/android/gms/maps/model/Marker; + public fun getMinClusterSize ()I + public fun onAdd ()V + protected fun onBeforeClusterItemRendered (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/AdvancedMarkerOptions;)V + protected fun onBeforeClusterRendered (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/AdvancedMarkerOptions;)V + protected fun onClusterItemRendered (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterItemUpdated (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterRendered (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterUpdated (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/AdvancedMarker;)V + public fun onClustersChanged (Ljava/util/Set;)V + public fun onRemove ()V + public fun setAnimation (Z)V + public fun setAnimationDuration (J)V + public fun setMinClusterSize (I)V + public fun setOnClusterClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterClickListener;)V + public fun setOnClusterInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowClickListener;)V + public fun setOnClusterInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowLongClickListener;)V + public fun setOnClusterItemClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemClickListener;)V + public fun setOnClusterItemInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowClickListener;)V + public fun setOnClusterItemInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowLongClickListener;)V + protected fun shouldRender (Ljava/util/Set;Ljava/util/Set;)Z + protected fun shouldRenderAsCluster (Lcom/google/maps/android/clustering/Cluster;)Z +} + +public final class com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer$Companion { +} + +public class com/google/maps/android/clustering/view/DefaultClusterRenderer : com/google/maps/android/clustering/view/ClusterRenderer { + public static final field Companion Lcom/google/maps/android/clustering/view/DefaultClusterRenderer$Companion; + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;)V + public fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;Ljava/util/concurrent/Executor;)V + public synthetic fun (Landroid/content/Context;Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/clustering/ClusterManager;Ljava/util/concurrent/Executor;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + protected fun getBucket (Lcom/google/maps/android/clustering/Cluster;)I + public fun getCluster (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/clustering/Cluster; + public fun getClusterItem (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/clustering/ClusterItem; + protected fun getClusterText (I)Ljava/lang/String; + public fun getClusterTextAppearance (I)I + public fun getColor (I)I + protected fun getDescriptorForCluster (Lcom/google/maps/android/clustering/Cluster;)Lcom/google/android/gms/maps/model/BitmapDescriptor; + public fun getMarker (Lcom/google/maps/android/clustering/Cluster;)Lcom/google/android/gms/maps/model/Marker; + public fun getMarker (Lcom/google/maps/android/clustering/ClusterItem;)Lcom/google/android/gms/maps/model/Marker; + public fun getMinClusterSize ()I + public fun onAdd ()V + protected fun onBeforeClusterItemRendered (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/MarkerOptions;)V + protected fun onBeforeClusterRendered (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/MarkerOptions;)V + protected fun onClusterItemRendered (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterItemUpdated (Lcom/google/maps/android/clustering/ClusterItem;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterRendered (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/Marker;)V + protected fun onClusterUpdated (Lcom/google/maps/android/clustering/Cluster;Lcom/google/android/gms/maps/model/Marker;)V + public fun onClustersChanged (Ljava/util/Set;)V + public fun onRemove ()V + public fun setAnimation (Z)V + public fun setAnimationDuration (J)V + public fun setMinClusterSize (I)V + public fun setOnClusterClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterClickListener;)V + public fun setOnClusterInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowClickListener;)V + public fun setOnClusterInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterInfoWindowLongClickListener;)V + public fun setOnClusterItemClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemClickListener;)V + public fun setOnClusterItemInfoWindowClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowClickListener;)V + public fun setOnClusterItemInfoWindowLongClickListener (Lcom/google/maps/android/clustering/ClusterManager$OnClusterItemInfoWindowLongClickListener;)V + protected fun shouldRender (Ljava/util/Set;Ljava/util/Set;)Z + protected fun shouldRenderAsCluster (Lcom/google/maps/android/clustering/Cluster;)Z +} + +public final class com/google/maps/android/clustering/view/DefaultClusterRenderer$Companion { +} + +public final class com/google/maps/android/geometry/Bounds { + public final field maxX D + public final field maxY D + public final field midX D + public final field midY D + public final field minX D + public final field minY D + public fun (DDDD)V + public final fun contains (DD)Z + public final fun contains (Lcom/google/maps/android/geometry/Bounds;)Z + public final fun contains (Lcom/google/maps/android/geometry/Point;)Z + public final fun intersects (DDDD)Z + public final fun intersects (Lcom/google/maps/android/geometry/Bounds;)Z +} + +public class com/google/maps/android/geometry/Point { + public final field x D + public final field y D + public fun (DD)V + public final fun copy (DD)Lcom/google/maps/android/geometry/Point; + public static synthetic fun copy$default (Lcom/google/maps/android/geometry/Point;DDILjava/lang/Object;)Lcom/google/maps/android/geometry/Point; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/geometry/PointExtensionsKt { + public static final fun component1 (Lcom/google/maps/android/geometry/Point;)D + public static final fun component2 (Lcom/google/maps/android/geometry/Point;)D +} + +public final class com/google/maps/android/ktx/utils/clustering/ClusterManagerKt { + public static final fun clusterClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterInfoWindowClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterInfoWindowLongClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterItemClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterItemInfoWindowClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; + public static final fun clusterItemInfoWindowLongClickEvents (Lcom/google/maps/android/clustering/ClusterManager;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/geometry/PointKt { + public static final fun component1 (Lcom/google/maps/android/geometry/Point;)D + public static final fun component2 (Lcom/google/maps/android/geometry/Point;)D +} + +public final class com/google/maps/android/projection/Point : com/google/maps/android/geometry/Point { + public fun (DD)V +} + +public final class com/google/maps/android/projection/SphericalMercatorProjection { + public fun (D)V + public final fun toLatLng (Lcom/google/maps/android/geometry/Point;)Lcom/google/android/gms/maps/model/LatLng; + public final fun toPoint (Lcom/google/android/gms/maps/model/LatLng;)Lcom/google/maps/android/geometry/Point; +} + +public final class com/google/maps/android/quadtree/PointQuadTree { + public static final field Companion Lcom/google/maps/android/quadtree/PointQuadTree$Companion; + public fun (DDDD)V + public fun (Lcom/google/maps/android/geometry/Bounds;)V + public fun (Lcom/google/maps/android/geometry/Bounds;I)V + public synthetic fun (Lcom/google/maps/android/geometry/Bounds;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun add (Lcom/google/maps/android/quadtree/PointQuadTree$Item;)V + public final fun clear ()V + public final fun remove (Lcom/google/maps/android/quadtree/PointQuadTree$Item;)Z + public final fun search (Lcom/google/maps/android/geometry/Bounds;)Ljava/util/Collection; +} + +public final class com/google/maps/android/quadtree/PointQuadTree$Companion { +} + +public abstract interface class com/google/maps/android/quadtree/PointQuadTree$Item { + public abstract fun getPoint ()Lcom/google/maps/android/geometry/Point; +} + diff --git a/clustering/build.gradle.kts b/clustering/build.gradle.kts index 13582eba6..c0a30741b 100644 --- a/clustering/build.gradle.kts +++ b/clustering/build.gradle.kts @@ -48,6 +48,7 @@ android { } kotlin { + explicitApi() compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/Cluster.kt b/clustering/src/main/java/com/google/maps/android/clustering/Cluster.kt index e239cec37..244292ae3 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/Cluster.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/Cluster.kt @@ -20,10 +20,10 @@ import com.google.android.gms.maps.model.LatLng /** * A collection of ClusterItems that are nearby each other. */ -interface Cluster { - val position: LatLng +public interface Cluster { + public val position: LatLng - val items: Collection + public val items: Collection - val size: Int + public val size: Int } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/ClusterItem.kt b/clustering/src/main/java/com/google/maps/android/clustering/ClusterItem.kt index e3409f893..ae2fc307a 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/ClusterItem.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/ClusterItem.kt @@ -20,24 +20,24 @@ import com.google.android.gms.maps.model.LatLng /** * ClusterItem represents a marker on the map. */ -interface ClusterItem { +public interface ClusterItem { /** * The position of this marker. This must always return the same value. */ - val position: LatLng + public val position: LatLng /** * The title of this marker. */ - val title: String? + public val title: String? /** * The description of this marker. */ - val snippet: String? + public val snippet: String? /** * The z-index of this marker. */ - val zIndex: Float? + public val zIndex: Float? } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/ClusterManager.kt b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManager.kt index 38f7c6e72..3071daed7 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/ClusterManager.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManager.kt @@ -46,17 +46,17 @@ import kotlin.jvm.JvmSuppressWildcards * - [com.google.android.gms.maps.GoogleMap.OnCameraIdleListener] * - [com.google.android.gms.maps.GoogleMap.OnMarkerClickListener] */ -open class ClusterManager +public open class ClusterManager @JvmOverloads - constructor( + public constructor( context: Context, private val mMap: GoogleMap, - val markerManager: MarkerManager = MarkerManager(mMap), + public val markerManager: MarkerManager = MarkerManager(mMap), ) : OnCameraIdleListener, OnMarkerClickListener, OnInfoWindowClickListener { - val markerCollection: MarkerManager.Collection = markerManager.newCollection() - val clusterMarkerCollection: MarkerManager.Collection = markerManager.newCollection() + public val markerCollection: MarkerManager.Collection = markerManager.newCollection() + public val clusterMarkerCollection: MarkerManager.Collection = markerManager.newCollection() private var mAlgorithm: ScreenBasedAlgorithm private var mRenderer: ClusterRenderer @@ -85,7 +85,7 @@ open class ClusterManager mRenderer.onAdd() } - open var renderer: ClusterRenderer + public open var renderer: ClusterRenderer get() = mRenderer set(value) { mRenderer.setOnClusterClickListener(null) @@ -104,7 +104,7 @@ open class ClusterManager cluster() } - open var algorithm: Algorithm + public open var algorithm: Algorithm get() = mAlgorithm set(value) { if (value is ScreenBasedAlgorithm<*>) { @@ -114,7 +114,7 @@ open class ClusterManager } } - open fun setAlgorithm(algorithm: ScreenBasedAlgorithm) { + public open fun setAlgorithm(algorithm: ScreenBasedAlgorithm) { algorithm.lock() try { val oldAlgorithm = this.algorithm @@ -137,7 +137,7 @@ open class ClusterManager cluster() } - open fun setAnimation(animate: Boolean) { + public open fun setAnimation(animate: Boolean) { mRenderer.setAnimation(animate) } @@ -145,7 +145,7 @@ open class ClusterManager * Removes all items from the cluster manager. After calling this method you must invoke * [.cluster] for the map to be cleared. */ - open fun clearItems() { + public open fun clearItems() { val algorithm = algorithm algorithm.lock() try { @@ -161,7 +161,7 @@ open class ClusterManager * @param items items to add to clusters * @return true if the cluster manager contents changed as a result of the call */ - open fun addItems(items: Collection<@JvmSuppressWildcards T>?): Boolean { + public open fun addItems(items: Collection<@JvmSuppressWildcards T>?): Boolean { val algorithm = algorithm algorithm.lock() try { @@ -177,7 +177,7 @@ open class ClusterManager * @param myItem item to add to clusters * @return true if the cluster manager contents changed as a result of the call */ - open fun addItem(myItem: T): Boolean { + public open fun addItem(myItem: T): Boolean { val algorithm = algorithm algorithm.lock() try { @@ -187,7 +187,7 @@ open class ClusterManager } } - open fun diff( + public open fun diff( add: Collection<@JvmSuppressWildcards T>?, remove: Collection<@JvmSuppressWildcards T>?, modify: Collection<@JvmSuppressWildcards T>?, @@ -224,7 +224,7 @@ open class ClusterManager * @param items items to remove from clusters * @return true if the cluster manager contents changed as a result of the call */ - open fun removeItems(items: Collection<@JvmSuppressWildcards T>?): Boolean { + public open fun removeItems(items: Collection<@JvmSuppressWildcards T>?): Boolean { val algorithm = algorithm algorithm.lock() try { @@ -240,7 +240,7 @@ open class ClusterManager * @param item item to remove from clusters * @return true if the item was removed from the cluster manager as a result of this call */ - open fun removeItem(item: T): Boolean { + public open fun removeItem(item: T): Boolean { val algorithm = algorithm algorithm.lock() try { @@ -257,7 +257,7 @@ open class ClusterManager * @return true if the item was updated in the cluster manager, false if the item is not * contained within the cluster manager and the cluster manager contents are unchanged */ - open fun updateItem(item: T): Boolean { + public open fun updateItem(item: T): Boolean { val algorithm = algorithm algorithm.lock() try { @@ -271,7 +271,7 @@ open class ClusterManager * Force a re-cluster on the map. You should call this after adding, removing, updating, * or clearing item(s). */ - open fun cluster() { + public open fun cluster() { mClusterTaskLock.writeLock().lock() try { // Attempt to cancel the in-flight request. @@ -327,7 +327,7 @@ open class ClusterManager * Sets a callback that's invoked when a Cluster is tapped. Note: For this listener to function, * the ClusterManager must be added as a click listener to the map. */ - open fun setOnClusterClickListener(listener: OnClusterClickListener?) { + public open fun setOnClusterClickListener(listener: OnClusterClickListener?) { mOnClusterClickListener = listener mRenderer.setOnClusterClickListener(listener) } @@ -336,7 +336,7 @@ open class ClusterManager * Sets a callback that's invoked when a Cluster info window is tapped. Note: For this listener to function, * the ClusterManager must be added as a info window click listener to the map. */ - open fun setOnClusterInfoWindowClickListener(listener: OnClusterInfoWindowClickListener?) { + public open fun setOnClusterInfoWindowClickListener(listener: OnClusterInfoWindowClickListener?) { mOnClusterInfoWindowClickListener = listener mRenderer.setOnClusterInfoWindowClickListener(listener) } @@ -345,7 +345,7 @@ open class ClusterManager * Sets a callback that's invoked when a Cluster info window is long-pressed. Note: For this listener to function, * the ClusterManager must be added as a info window click listener to the map. */ - open fun setOnClusterInfoWindowLongClickListener(listener: OnClusterInfoWindowLongClickListener?) { + public open fun setOnClusterInfoWindowLongClickListener(listener: OnClusterInfoWindowLongClickListener?) { mOnClusterInfoWindowLongClickListener = listener mRenderer.setOnClusterInfoWindowLongClickListener(listener) } @@ -354,7 +354,7 @@ open class ClusterManager * Sets a callback that's invoked when an individual ClusterItem is tapped. Note: For this * listener to function, the ClusterManager must be added as a click listener to the map. */ - open fun setOnClusterItemClickListener(listener: OnClusterItemClickListener?) { + public open fun setOnClusterItemClickListener(listener: OnClusterItemClickListener?) { mOnClusterItemClickListener = listener mRenderer.setOnClusterItemClickListener(listener) } @@ -363,7 +363,7 @@ open class ClusterManager * Sets a callback that's invoked when an individual ClusterItem's Info Window is tapped. Note: For this * listener to function, the ClusterManager must be added as a info window click listener to the map. */ - open fun setOnClusterItemInfoWindowClickListener(listener: OnClusterItemInfoWindowClickListener?) { + public open fun setOnClusterItemInfoWindowClickListener(listener: OnClusterItemInfoWindowClickListener?) { mOnClusterItemInfoWindowClickListener = listener mRenderer.setOnClusterItemInfoWindowClickListener(listener) } @@ -372,7 +372,7 @@ open class ClusterManager * Sets a callback that's invoked when an individual ClusterItem's Info Window is long-pressed. Note: For this * listener to function, the ClusterManager must be added as a info window click listener to the map. */ - open fun setOnClusterItemInfoWindowLongClickListener(listener: OnClusterItemInfoWindowLongClickListener?) { + public open fun setOnClusterItemInfoWindowLongClickListener(listener: OnClusterItemInfoWindowLongClickListener?) { mOnClusterItemInfoWindowLongClickListener = listener mRenderer.setOnClusterItemInfoWindowLongClickListener(listener) } @@ -380,33 +380,33 @@ open class ClusterManager /** * Called when a Cluster is clicked. */ - fun interface OnClusterClickListener { + public fun interface OnClusterClickListener { /** - * Called when cluster is clicked. - * Return true if click has been handled - * Return false and the click will dispatched to the next listener - */ - fun onClusterClick(cluster: Cluster): Boolean + * Called when cluster is clicked. + * Return true if click has been handled + * Return false and the click will dispatched to the next listener + */ + public fun onClusterClick(cluster: Cluster): Boolean } /** * Called when a Cluster's Info Window is clicked. */ - fun interface OnClusterInfoWindowClickListener { - fun onClusterInfoWindowClick(cluster: Cluster) + public fun interface OnClusterInfoWindowClickListener { + public fun onClusterInfoWindowClick(cluster: Cluster) } /** * Called when a Cluster's Info Window is long clicked. */ - fun interface OnClusterInfoWindowLongClickListener { - fun onClusterInfoWindowLongClick(cluster: Cluster) + public fun interface OnClusterInfoWindowLongClickListener { + public fun onClusterInfoWindowLongClick(cluster: Cluster) } /** * Called when an individual ClusterItem is clicked. */ - fun interface OnClusterItemClickListener { + public fun interface OnClusterItemClickListener { /** * Called when `item` is clicked. * @@ -416,20 +416,20 @@ open class ClusterManager * occur), false otherwise (i.e. the default behavior should occur). The default behavior * is for the camera to move to the marker and an info window to appear. */ - fun onClusterItemClick(item: T): Boolean + public fun onClusterItemClick(item: T): Boolean } /** * Called when an individual ClusterItem's Info Window is clicked. */ - fun interface OnClusterItemInfoWindowClickListener { - fun onClusterItemInfoWindowClick(item: T) + public fun interface OnClusterItemInfoWindowClickListener { + public fun onClusterItemInfoWindowClick(item: T) } /** * Called when an individual ClusterItem's Info Window is long clicked. */ - fun interface OnClusterItemInfoWindowLongClickListener { - fun onClusterItemInfoWindowLongClick(item: T) + public fun interface OnClusterItemInfoWindowLongClickListener { + public fun onClusterItemInfoWindowLongClick(item: T) } } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/AbstractAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/AbstractAlgorithm.kt index 8ba76a038..639a1ed80 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/AbstractAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/AbstractAlgorithm.kt @@ -22,7 +22,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock /** * Base Algorithm class that implements lock/unlock functionality. */ -abstract class AbstractAlgorithm : Algorithm { +public abstract class AbstractAlgorithm : Algorithm { private val mLock: ReadWriteLock = ReentrantReadWriteLock() override fun lock() { diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/Algorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/Algorithm.kt index ab6b67e58..c5904d58d 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/Algorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/Algorithm.kt @@ -21,22 +21,22 @@ import com.google.maps.android.clustering.ClusterItem /** * Logic for computing clusters */ -interface Algorithm { +public interface Algorithm { /** * Adds an item to the algorithm * @param item the item to be added * @return true if the algorithm contents changed as a result of the call */ - fun addItem(item: T): Boolean + public fun addItem(item: T): Boolean /** * Adds a collection of items to the algorithm * @param items the items to be added * @return true if the algorithm contents changed as a result of the call */ - fun addItems(items: Collection): Boolean + public fun addItems(items: Collection): Boolean - fun clearItems() + public fun clearItems() /** * Removes an item from the algorithm @@ -44,7 +44,7 @@ interface Algorithm { * @return true if this algorithm contained the specified element (or equivalently, if this * algorithm changed as a result of the call). */ - fun removeItem(item: T): Boolean + public fun removeItem(item: T): Boolean /** * Updates the provided item in the algorithm @@ -52,22 +52,22 @@ interface Algorithm { * @return true if the item existed in the algorithm and was updated, or false if the item did * not exist in the algorithm and the algorithm contents remain unchanged. */ - fun updateItem(item: T): Boolean + public fun updateItem(item: T): Boolean /** * Removes a collection of items from the algorithm * @param items the items to be removed * @return true if this algorithm contents changed as a result of the call */ - fun removeItems(items: Collection): Boolean + public fun removeItems(items: Collection): Boolean - fun getClusters(zoom: Float): Set> + public fun getClusters(zoom: Float): Set> - val items: Collection + public val items: Collection - var maxDistanceBetweenClusteredItems: Int + public var maxDistanceBetweenClusteredItems: Int - fun lock() + public fun lock() - fun unlock() + public fun unlock() } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/CentroidNonHierarchicalDistanceBasedAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/CentroidNonHierarchicalDistanceBasedAlgorithm.kt index 521e7971f..ca66c4fc7 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/CentroidNonHierarchicalDistanceBasedAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/CentroidNonHierarchicalDistanceBasedAlgorithm.kt @@ -31,7 +31,7 @@ import java.util.HashSet * * @param the type of cluster item */ -open class CentroidNonHierarchicalDistanceBasedAlgorithm : NonHierarchicalDistanceBasedAlgorithm() { +public open class CentroidNonHierarchicalDistanceBasedAlgorithm : NonHierarchicalDistanceBasedAlgorithm() { /** * Computes the centroid (average latitude and longitude) of a collection of cluster items. * diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.kt index b9c048aca..b3c40c551 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.kt @@ -31,7 +31,7 @@ import kotlin.math.pow * * @param the type of cluster item */ -class ContinuousZoomEuclideanCentroidAlgorithm : CentroidNonHierarchicalDistanceBasedAlgorithm() { +public open class ContinuousZoomEuclideanCentroidAlgorithm : CentroidNonHierarchicalDistanceBasedAlgorithm() { override fun getClusters(zoom: Float): Set> { // Continuous zoom — no casting to int val zoomSpecificSpan = maxDistanceBetweenClusteredItems.toDouble() / 2.0.pow(zoom.toDouble()) / 256.0 diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/GridBasedAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/GridBasedAlgorithm.kt index 4a77032b8..ad0c7f370 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/GridBasedAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/GridBasedAlgorithm.kt @@ -35,7 +35,7 @@ import kotlin.math.pow * * @param The type of {@link ClusterItem} to be clustered. */ -class GridBasedAlgorithm : AbstractAlgorithm() { +public open class GridBasedAlgorithm : AbstractAlgorithm() { private var mGridSize = DEFAULT_GRID_SIZE private val mItems: MutableSet = Collections.synchronizedSet(HashSet()) @@ -103,7 +103,7 @@ class GridBasedAlgorithm : AbstractAlgorithm() { override val items: Collection get() = mItems - companion object { + public companion object { private const val DEFAULT_GRID_SIZE = 100 private fun getCoord( diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.kt index 5fcac43e9..948100062 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.kt @@ -40,13 +40,13 @@ import java.util.LinkedHashSet * * Clusters have the center of the first element (not the centroid of the items within it). */ -open class NonHierarchicalDistanceBasedAlgorithm : AbstractAlgorithm() { +public open class NonHierarchicalDistanceBasedAlgorithm : AbstractAlgorithm() { /** * Any modifications should be synchronized on mQuadTree. */ @JvmField protected val mItems: MutableCollection> = LinkedHashSet() - protected val mItemMap = HashMap>() + protected val mItemMap: MutableMap> = HashMap() /** * Any modifications should be synchronized on mQuadTree. @@ -230,7 +230,7 @@ open class NonHierarchicalDistanceBasedAlgorithm : AbstractAlgo } protected class QuadItem( - @JvmField val mClusterItem: T, + @JvmField public val mClusterItem: T, ) : PointQuadTree.Item, Cluster { private val mPoint: Point = PROJECTION.toPoint(mClusterItem.position) @@ -260,7 +260,7 @@ open class NonHierarchicalDistanceBasedAlgorithm : AbstractAlgo } } - companion object { + public companion object { private const val DEFAULT_MAX_DISTANCE_AT_ZOOM = 100 // essentially 100 dp. private val PROJECTION = SphericalMercatorProjection(1.0) diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm.kt index bdd28d678..2ed7f1a66 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalViewBasedAlgorithm.kt @@ -31,12 +31,12 @@ import kotlin.math.pow * * @param The [ClusterItem] type */ -class NonHierarchicalViewBasedAlgorithm +public open class NonHierarchicalViewBasedAlgorithm /** * @param screenWidth map width in dp * @param screenHeight map height in dp */ - (private var mViewWidth: Int, private var mViewHeight: Int) : NonHierarchicalDistanceBasedAlgorithm(), ScreenBasedAlgorithm { + public constructor(private var mViewWidth: Int, private var mViewHeight: Int) : NonHierarchicalDistanceBasedAlgorithm(), ScreenBasedAlgorithm { private var mMapCenter: LatLng? = null @@ -75,7 +75,7 @@ class NonHierarchicalViewBasedAlgorithm * @param width map width in dp * @param height map height in dp */ - fun updateViewSize(width: Int, height: Int) { + public fun updateViewSize(width: Int, height: Int) { mViewWidth = width mViewHeight = height } @@ -96,7 +96,7 @@ class NonHierarchicalViewBasedAlgorithm ) } - companion object { + public companion object { private val PROJECTION = SphericalMercatorProjection(1.0) } } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/PreCachingAlgorithmDecorator.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/PreCachingAlgorithmDecorator.kt index 9ae93c972..8ac578260 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/PreCachingAlgorithmDecorator.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/PreCachingAlgorithmDecorator.kt @@ -27,7 +27,7 @@ import kotlin.concurrent.withLock /** * Optimistically fetch clusters for adjacent zoom levels, caching them as necessary. */ -class PreCachingAlgorithmDecorator( +public open class PreCachingAlgorithmDecorator public constructor( private val algorithm: Algorithm, ) : AbstractAlgorithm() { // TODO: evaluate maxSize parameter for LruCache. diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithm.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithm.kt index f6aab988f..193d55fbe 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithm.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithm.kt @@ -25,8 +25,8 @@ import com.google.maps.android.clustering.ClusterItem * * @param The {@link ClusterItem} type */ -interface ScreenBasedAlgorithm : Algorithm { - fun shouldReclusterOnMapMovement(): Boolean +public interface ScreenBasedAlgorithm : Algorithm { + public fun shouldReclusterOnMapMovement(): Boolean - fun onCameraChange(position: CameraPosition) + public fun onCameraChange(position: CameraPosition) } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithmAdapter.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithmAdapter.kt index 52f08c02d..dd04abc70 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithmAdapter.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/ScreenBasedAlgorithmAdapter.kt @@ -19,7 +19,7 @@ import com.google.android.gms.maps.model.CameraPosition import com.google.maps.android.clustering.Cluster import com.google.maps.android.clustering.ClusterItem -class ScreenBasedAlgorithmAdapter( +public open class ScreenBasedAlgorithmAdapter public constructor( private val algorithm: Algorithm, ) : AbstractAlgorithm(), ScreenBasedAlgorithm { diff --git a/clustering/src/main/java/com/google/maps/android/clustering/algo/StaticCluster.kt b/clustering/src/main/java/com/google/maps/android/clustering/algo/StaticCluster.kt index e292b6e33..a10d87160 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/algo/StaticCluster.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/algo/StaticCluster.kt @@ -22,17 +22,17 @@ import com.google.maps.android.clustering.ClusterItem /** * A cluster whose center is determined upon creation. */ -class StaticCluster( +public open class StaticCluster public constructor( private val mCenter: LatLng, ) : Cluster { private val mItems: MutableCollection = LinkedHashSet() - fun add(t: T): Boolean = mItems.add(t) + public fun add(t: T): Boolean = mItems.add(t) override val position: LatLng get() = mCenter - fun remove(t: T): Boolean = mItems.remove(t) + public fun remove(t: T): Boolean = mItems.remove(t) override val items: Collection get() = mItems diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRenderer.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRenderer.kt index 02a200b4a..0f882bd47 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRenderer.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRenderer.kt @@ -28,54 +28,54 @@ import com.google.maps.android.clustering.ClusterManager.OnClusterItemInfoWindow /** * Renders clusters. */ -interface ClusterRenderer { +public interface ClusterRenderer { /** * Called when the view needs to be updated because new clusters need to be displayed. * * @param clusters the clusters to be displayed. */ - fun onClustersChanged(clusters: Set>) + public fun onClustersChanged(clusters: Set>) - fun setOnClusterClickListener(listener: OnClusterClickListener?) + public fun setOnClusterClickListener(listener: OnClusterClickListener?) - fun setOnClusterInfoWindowClickListener(listener: OnClusterInfoWindowClickListener?) + public fun setOnClusterInfoWindowClickListener(listener: OnClusterInfoWindowClickListener?) - fun setOnClusterInfoWindowLongClickListener(listener: OnClusterInfoWindowLongClickListener?) + public fun setOnClusterInfoWindowLongClickListener(listener: OnClusterInfoWindowLongClickListener?) - fun setOnClusterItemClickListener(listener: OnClusterItemClickListener?) + public fun setOnClusterItemClickListener(listener: OnClusterItemClickListener?) - fun setOnClusterItemInfoWindowClickListener(listener: OnClusterItemInfoWindowClickListener?) + public fun setOnClusterItemInfoWindowClickListener(listener: OnClusterItemInfoWindowClickListener?) - fun setOnClusterItemInfoWindowLongClickListener(listener: OnClusterItemInfoWindowLongClickListener?) + public fun setOnClusterItemInfoWindowLongClickListener(listener: OnClusterItemInfoWindowLongClickListener?) /** * Called to set animation on or off */ - fun setAnimation(animate: Boolean) + public fun setAnimation(animate: Boolean) /** * Sets the length of the animation in milliseconds. */ - fun setAnimationDuration(animationDurationMs: Long) + public fun setAnimationDuration(animationDurationMs: Long) /** * Called when the view is added. */ - fun onAdd() + public fun onAdd() /** * Called when the view is removed. */ - fun onRemove() + public fun onRemove() /** * Called to determine the color of a Cluster. */ - fun getColor(clusterSize: Int): Int + public fun getColor(clusterSize: Int): Int /** * Called to determine the text appearance of a cluster. */ @StyleRes - fun getClusterTextAppearance(clusterSize: Int): Int + public fun getClusterTextAppearance(clusterSize: Int): Int } diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt index b65ea8f0e..d3435ce88 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/ClusterRendererMultipleItems.kt @@ -73,7 +73,7 @@ import kotlin.math.sign /** * The default view for a ClusterManager. Markers are animated in and out of clusters. */ -open class ClusterRendererMultipleItems @JvmOverloads constructor( +public open class ClusterRendererMultipleItems @JvmOverloads public constructor( context: Context, private val mMap: GoogleMap, private val mClusterManager: ClusterManager, @@ -90,7 +90,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct // Default interpolator private var animationInterp: TimeInterpolator = DecelerateInterpolator() - enum class AnimationType { + public enum class AnimationType { LINEAR, EASE_IN, EASE_OUT, @@ -101,7 +101,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct DECELERATE, } - fun setAnimationType(type: AnimationType) { + public fun setAnimationType(type: AnimationType) { animationInterp = when (type) { AnimationType.LINEAR -> LinearInterpolator() @@ -118,7 +118,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct * * @param interpolator the interpolator to use for the animation. */ - fun setAnimationInterpolator(interpolator: TimeInterpolator) { + public fun setAnimationInterpolator(interpolator: TimeInterpolator) { animationInterp = interpolator } @@ -140,7 +140,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct /** * If cluster size is less than this size, display individual markers. */ - var minClusterSize: Int = 2 + public open var minClusterSize: Int = 2 /** * The currently displayed set of clusters. @@ -263,7 +263,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct * * @param enabled `true` to enable logging; `false` to disable it. */ - fun setLoggingEnabled(enabled: Boolean) { + public fun setLoggingEnabled(enabled: Boolean) { RendererLogger.setEnabled(enabled) } @@ -577,7 +577,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct mAnimationDurationMs = animationDurationMs } - fun stopAnimation() { + public fun stopAnimation() { for (animation in ongoingAnimations) { animation.cancel() } @@ -1048,7 +1048,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct * @param clusterItem ClusterItem which you will obtain its marker * @return a marker from a ClusterItem or null if it does not exists */ - fun getMarker(clusterItem: T): Marker? = mMarkerCache[clusterItem] + public open fun getMarker(clusterItem: T): Marker? = mMarkerCache[clusterItem] /** * Get the ClusterItem from a marker @@ -1056,7 +1056,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct * @param marker which you will obtain its ClusterItem * @return a ClusterItem from a marker or null if it does not exists */ - fun getClusterItem(marker: Marker): T? = mMarkerCache[marker] + public open fun getClusterItem(marker: Marker): T? = mMarkerCache[marker] /** * Get the marker from a Cluster @@ -1064,7 +1064,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct * @param cluster which you will obtain its marker * @return a marker from a cluster or null if it does not exists */ - fun getMarker(cluster: Cluster): Marker? = mClusterMarkerCache[cluster] + public open fun getMarker(cluster: Cluster): Marker? = mClusterMarkerCache[cluster] /** * Get the Cluster from a marker @@ -1072,7 +1072,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct * @param marker which you will obtain its Cluster * @return a Cluster from a marker or null if it does not exists */ - fun getCluster(marker: Marker): Cluster? = mClusterMarkerCache[marker] + public open fun getCluster(marker: Marker): Cluster? = mClusterMarkerCache[marker] /** * Creates markerWithPosition(s) for a particular cluster, animating it if necessary. @@ -1273,7 +1273,7 @@ open class ClusterRendererMultipleItems @JvmOverloads construct } } - companion object { + public companion object { private val BUCKETS = intArrayOf(10, 20, 50, 100, 200, 500, 1000) private const val RUN_TASK = 0 private const val TASK_FINISHED = 1 diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt index 86903c2ef..c22107b76 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultAdvancedMarkersClusterRenderer.kt @@ -69,7 +69,7 @@ import kotlin.math.sign /** * The default view for a ClusterManager. Markers are animated in and out of clusters. */ -open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads constructor( +public open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads public constructor( context: Context, private val mMap: GoogleMap, private val mClusterManager: ClusterManager, @@ -102,7 +102,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads /** * If cluster size is less than this size, display individual markers. */ - var minClusterSize: Int = 4 + public open var minClusterSize: Int = 4 /** * The currently displayed set of clusters. @@ -977,7 +977,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads * @param clusterItem ClusterItem which you will obtain its marker * @return a marker from a ClusterItem or null if it does not exists */ - fun getMarker(clusterItem: T): Marker? = mMarkerCache[clusterItem] + public open fun getMarker(clusterItem: T): Marker? = mMarkerCache[clusterItem] /** * Get the ClusterItem from a marker @@ -985,7 +985,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads * @param marker which you will obtain its ClusterItem * @return a ClusterItem from a marker or null if it does not exists */ - fun getClusterItem(marker: Marker): T? = mMarkerCache[marker] + public open fun getClusterItem(marker: Marker): T? = mMarkerCache[marker] /** * Get the marker from a Cluster @@ -993,7 +993,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads * @param cluster which you will obtain its marker * @return a marker from a cluster or null if it does not exists */ - fun getMarker(cluster: Cluster): Marker? = mClusterMarkerCache[cluster] + public open fun getMarker(cluster: Cluster): Marker? = mClusterMarkerCache[cluster] /** * Get the Cluster from a marker @@ -1001,7 +1001,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads * @param marker which you will obtain its Cluster * @return a Cluster from a marker or null if it does not exists */ - fun getCluster(marker: Marker): Cluster? = mClusterMarkerCache[marker] + public open fun getCluster(marker: Marker): Cluster? = mClusterMarkerCache[marker] /** * Creates markerWithPosition(s) for a particular cluster, animating it if necessary. @@ -1137,7 +1137,7 @@ open class DefaultAdvancedMarkersClusterRenderer @JvmOverloads } } - companion object { + public companion object { private val BUCKETS = intArrayOf(10, 20, 50, 100, 200, 500, 1000) private val ANIMATION_INTERP: TimeInterpolator = DecelerateInterpolator() private const val RUN_TASK = 0 diff --git a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt index ba77a72b6..d59d406a0 100644 --- a/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt +++ b/clustering/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.kt @@ -68,7 +68,7 @@ import kotlin.math.sign /** * The default view for a ClusterManager. Markers are animated in and out of clusters. */ -open class DefaultClusterRenderer @JvmOverloads constructor( +public open class DefaultClusterRenderer @JvmOverloads public constructor( context: Context, private val mMap: GoogleMap, private val mClusterManager: ClusterManager, @@ -101,7 +101,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( /** * If cluster size is less than this size, display individual markers. */ - var minClusterSize: Int = 4 + public open var minClusterSize: Int = 4 /** * The currently displayed set of clusters. @@ -986,7 +986,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( * @param clusterItem ClusterItem which you will obtain its marker * @return a marker from a ClusterItem or null if it does not exists */ - fun getMarker(clusterItem: T): Marker? = mMarkerCache[clusterItem] + public open fun getMarker(clusterItem: T): Marker? = mMarkerCache[clusterItem] /** * Get the ClusterItem from a marker @@ -994,7 +994,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( * @param marker which you will obtain its ClusterItem * @return a ClusterItem from a marker or null if it does not exists */ - fun getClusterItem(marker: Marker): T? = mMarkerCache[marker] + public open fun getClusterItem(marker: Marker): T? = mMarkerCache[marker] /** * Get the marker from a Cluster @@ -1002,7 +1002,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( * @param cluster which you will obtain its marker * @return a marker from a cluster or null if it does not exists */ - fun getMarker(cluster: Cluster): Marker? = mClusterMarkerCache[cluster] + public open fun getMarker(cluster: Cluster): Marker? = mClusterMarkerCache[cluster] /** * Get the Cluster from a marker @@ -1010,7 +1010,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( * @param marker which you will obtain its Cluster * @return a Cluster from a marker or null if it does not exists */ - fun getCluster(marker: Marker): Cluster? = mClusterMarkerCache[marker] + public open fun getCluster(marker: Marker): Cluster? = mClusterMarkerCache[marker] /** * Creates markerWithPosition(s) for a particular cluster, animating it if necessary. @@ -1144,7 +1144,7 @@ open class DefaultClusterRenderer @JvmOverloads constructor( } } - companion object { + public companion object { private val BUCKETS = intArrayOf(10, 20, 50, 100, 200, 500, 1000) private val ANIMATION_INTERP: TimeInterpolator = DecelerateInterpolator() private const val RUN_TASK = 0 diff --git a/clustering/src/main/java/com/google/maps/android/geometry/Bounds.kt b/clustering/src/main/java/com/google/maps/android/geometry/Bounds.kt index cafd350b2..c342a58bd 100755 --- a/clustering/src/main/java/com/google/maps/android/geometry/Bounds.kt +++ b/clustering/src/main/java/com/google/maps/android/geometry/Bounds.kt @@ -18,33 +18,33 @@ package com.google.maps.android.geometry /** * Represents an area in the cartesian plane. */ -class Bounds( - @JvmField val minX: Double, - @JvmField val maxX: Double, - @JvmField val minY: Double, - @JvmField val maxY: Double, +public class Bounds( + @JvmField public val minX: Double, + @JvmField public val maxX: Double, + @JvmField public val minY: Double, + @JvmField public val maxY: Double, ) { @JvmField - val midX: Double = (minX + maxX) / 2 + public val midX: Double = (minX + maxX) / 2 @JvmField - val midY: Double = (minY + maxY) / 2 + public val midY: Double = (minY + maxY) / 2 - fun contains( + public fun contains( x: Double, y: Double, ): Boolean = minX <= x && x <= maxX && minY <= y && y <= maxY - fun contains(point: Point): Boolean = contains(point.x, point.y) + public fun contains(point: Point): Boolean = contains(point.x, point.y) - fun intersects( + public fun intersects( minX: Double, maxX: Double, minY: Double, maxY: Double, ): Boolean = minX < this.maxX && this.minX < maxX && minY < this.maxY && this.minY < maxY - fun intersects(bounds: Bounds): Boolean = intersects(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY) + public fun intersects(bounds: Bounds): Boolean = intersects(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY) - fun contains(bounds: Bounds): Boolean = bounds.minX >= minX && bounds.maxX <= maxX && bounds.minY >= minY && bounds.maxY <= maxY + public fun contains(bounds: Bounds): Boolean = bounds.minX >= minX && bounds.maxX <= maxX && bounds.minY >= minY && bounds.maxY <= maxY } diff --git a/clustering/src/main/java/com/google/maps/android/geometry/Point.kt b/clustering/src/main/java/com/google/maps/android/geometry/Point.kt index 14038e200..69b3fd549 100644 --- a/clustering/src/main/java/com/google/maps/android/geometry/Point.kt +++ b/clustering/src/main/java/com/google/maps/android/geometry/Point.kt @@ -15,9 +15,9 @@ */ package com.google.maps.android.geometry -open class Point( - @JvmField val x: Double, - @JvmField val y: Double, +public open class Point( + @JvmField public val x: Double, + @JvmField public val y: Double, ) { override fun toString(): String = "Point(x=$x, y=$y)" @@ -39,7 +39,7 @@ open class Point( return result } - fun copy( + public fun copy( x: Double = this.x, y: Double = this.y, ): Point = Point(x, y) diff --git a/clustering/src/main/java/com/google/maps/android/projection/SphericalMercatorProjection.kt b/clustering/src/main/java/com/google/maps/android/projection/SphericalMercatorProjection.kt index 971a81bdd..377dfd533 100644 --- a/clustering/src/main/java/com/google/maps/android/projection/SphericalMercatorProjection.kt +++ b/clustering/src/main/java/com/google/maps/android/projection/SphericalMercatorProjection.kt @@ -18,10 +18,10 @@ package com.google.maps.android.projection import com.google.android.gms.maps.model.LatLng import kotlin.math.* -class SphericalMercatorProjection( +public class SphericalMercatorProjection public constructor( private val worldWidth: Double, ) { - fun toPoint(latLng: LatLng): com.google.maps.android.geometry.Point { + public fun toPoint(latLng: LatLng): com.google.maps.android.geometry.Point { val x = latLng.longitude / 360 + .5 val siny = sin(Math.toRadians(latLng.latitude)) val y = 0.5 * ln((1 + siny) / (1 - siny)) / -(2 * PI) + .5 @@ -29,7 +29,7 @@ class SphericalMercatorProjection( return com.google.maps.android.geometry.Point(x * worldWidth, y * worldWidth) } - fun toLatLng(point: com.google.maps.android.geometry.Point): LatLng { + public fun toLatLng(point: com.google.maps.android.geometry.Point): LatLng { val x = point.x / worldWidth - 0.5 val lng = x * 360 diff --git a/clustering/src/main/java/com/google/maps/android/quadtree/PointQuadTree.kt b/clustering/src/main/java/com/google/maps/android/quadtree/PointQuadTree.kt index 2227e5c9d..e20677379 100644 --- a/clustering/src/main/java/com/google/maps/android/quadtree/PointQuadTree.kt +++ b/clustering/src/main/java/com/google/maps/android/quadtree/PointQuadTree.kt @@ -23,14 +23,14 @@ import com.google.maps.android.geometry.Point * See http://en.wikipedia.org/wiki/Quadtree for details on the data structure. * This class is not thread safe. */ -class PointQuadTree +public class PointQuadTree @JvmOverloads - constructor( + public constructor( private val mBounds: Bounds, private val mDepth: Int = 0, ) { - interface Item { - val point: Point + public interface Item { + public val point: Point } /** @@ -43,13 +43,13 @@ class PointQuadTree */ private var mChildren: MutableList>? = null - constructor(minX: Double, maxX: Double, minY: Double, maxY: Double) : + public constructor(minX: Double, maxX: Double, minY: Double, maxY: Double) : this(Bounds(minX, maxX, minY, maxY)) /** * Insert an item. */ - fun add(item: T) { + public fun add(item: T) { val point = item.point if (this.mBounds.contains(point.x, point.y)) { insert(point.x, point.y, item) @@ -112,7 +112,7 @@ class PointQuadTree * * @return whether the item was removed. */ - fun remove(item: T): Boolean { + public fun remove(item: T): Boolean { val point = item.point return if (this.mBounds.contains(point.x, point.y)) { remove(point.x, point.y, item) @@ -151,7 +151,7 @@ class PointQuadTree /** * Removes all points from the quadTree */ - fun clear() { + public fun clear() { mChildren = null if (mItems != null) { mItems!!.clear() @@ -161,7 +161,7 @@ class PointQuadTree /** * Search for all items within a given bounds. */ - fun search(searchBounds: Bounds): Collection { + public fun search(searchBounds: Bounds): Collection { val results: MutableList = ArrayList() search(searchBounds, results) return results @@ -192,7 +192,7 @@ class PointQuadTree } } - companion object { + public companion object { /** * Maximum number of elements to store in a quad before splitting. */ diff --git a/data/api/data.api b/data/api/data.api new file mode 100644 index 000000000..1045b2746 --- /dev/null +++ b/data/api/data.api @@ -0,0 +1,2518 @@ +public final class com/google/maps/android/data/DataLayerLoader { + public fun (Landroid/content/Context;Lkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (Landroid/content/Context;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun loadAsset (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun loadInputStream (Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class com/google/maps/android/data/DataPolygon : com/google/maps/android/data/Geometry { + public abstract fun getInnerBoundaryCoordinates ()Ljava/util/List; + public abstract fun getOuterBoundaryCoordinates ()Ljava/util/List; +} + +public class com/google/maps/android/data/Feature : java/util/Observable { + public fun (Lcom/google/maps/android/data/Geometry;Ljava/lang/String;Ljava/util/Map;)V + public final fun getGeometry ()Lcom/google/maps/android/data/Geometry; + public final fun getId ()Ljava/lang/String; + protected final fun getMId ()Ljava/lang/String; + public final fun getProperties ()Ljava/lang/Iterable; + public final fun getProperty (Ljava/lang/String;)Ljava/lang/String; + public final fun getPropertyKeys ()Ljava/lang/Iterable; + public final fun hasGeometry ()Z + public final fun hasProperties ()Z + public final fun hasProperty (Ljava/lang/String;)Z + protected fun removeProperty (Ljava/lang/String;)Ljava/lang/String; + protected fun setGeometry (Lcom/google/maps/android/data/Geometry;)V + protected final fun setMId (Ljava/lang/String;)V + protected fun setProperty (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/Geometry { + public abstract fun getGeometryObject ()Ljava/lang/Object; + public abstract fun getGeometryType ()Ljava/lang/String; +} + +public abstract class com/google/maps/android/data/Layer { + public fun ()V + public abstract fun addLayerToMap ()V + public fun getDefaultLineStringStyle ()Lcom/google/maps/android/data/geojson/GeoJsonLineStringStyle; + public fun getDefaultPointStyle ()Lcom/google/maps/android/data/geojson/GeoJsonPointStyle; + public fun getDefaultPolygonStyle ()Lcom/google/maps/android/data/geojson/GeoJsonPolygonStyle; + public abstract fun getFeatures ()Ljava/lang/Iterable; + protected final fun getMDefaultLineStringStyle ()Lcom/google/maps/android/data/geojson/GeoJsonLineStringStyle; + protected final fun getMDefaultPointStyle ()Lcom/google/maps/android/data/geojson/GeoJsonPointStyle; + protected final fun getMDefaultPolygonStyle ()Lcom/google/maps/android/data/geojson/GeoJsonPolygonStyle; + protected final fun getMGoogleMap ()Lcom/google/android/gms/maps/GoogleMap; + public abstract fun getMap ()Lcom/google/android/gms/maps/GoogleMap; + public abstract fun isLayerOnMap ()Z + public abstract fun removeLayerFromMap ()V + protected final fun setMDefaultLineStringStyle (Lcom/google/maps/android/data/geojson/GeoJsonLineStringStyle;)V + protected final fun setMDefaultPointStyle (Lcom/google/maps/android/data/geojson/GeoJsonPointStyle;)V + protected final fun setMDefaultPolygonStyle (Lcom/google/maps/android/data/geojson/GeoJsonPolygonStyle;)V + protected final fun setMGoogleMap (Lcom/google/android/gms/maps/GoogleMap;)V + public abstract fun setMap (Lcom/google/android/gms/maps/GoogleMap;)V + public fun setOnFeatureClickListener (Lcom/google/maps/android/data/Layer$OnFeatureClickListener;)V +} + +public abstract interface class com/google/maps/android/data/Layer$OnFeatureClickListener { + public abstract fun onFeatureClick (Lcom/google/maps/android/data/Feature;)V +} + +public class com/google/maps/android/data/LineString : com/google/maps/android/data/Geometry { + public fun (Ljava/util/List;)V + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryObject ()Ljava/util/List; + public fun getGeometryType ()Ljava/lang/String; +} + +public class com/google/maps/android/data/MultiGeometry : com/google/maps/android/data/Geometry { + public fun (Ljava/util/List;)V + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryObject ()Ljava/util/List; + public fun getGeometryType ()Ljava/lang/String; + public final fun setGeometryType (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public class com/google/maps/android/data/Point : com/google/maps/android/data/Geometry { + public fun (Lcom/google/android/gms/maps/model/LatLng;)V + public fun getGeometryObject ()Lcom/google/android/gms/maps/model/LatLng; + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryType ()Ljava/lang/String; +} + +public class com/google/maps/android/data/Renderer { + public fun ()V +} + +public final class com/google/maps/android/data/Renderer$ImagesCache { + public fun ()V +} + +public final class com/google/maps/android/data/RendererLogger { + public static final field INSTANCE Lcom/google/maps/android/data/RendererLogger; + public static final fun d (Ljava/lang/String;Ljava/lang/String;)V + public static final fun e (Ljava/lang/String;Ljava/lang/String;)V + public static final fun setEnabled (Z)V +} + +public abstract class com/google/maps/android/data/Style : java/util/Observable { + public static final field Companion Lcom/google/maps/android/data/Style$Companion; + public fun ()V + public fun getRotation ()F + public fun setLineStringWidth (F)V + public fun setMarkerHotSpot (FFLjava/lang/String;Ljava/lang/String;)V + public fun setMarkerRotation (F)V + public fun setPolygonFillColor (I)V + public fun setPolygonStrokeWidth (F)V +} + +public final class com/google/maps/android/data/Style$Companion { +} + +public final class com/google/maps/android/data/geojson/GeoJsonFeature : com/google/maps/android/data/Feature, java/util/Observer { + public fun (Lcom/google/maps/android/data/Geometry;Ljava/lang/String;Ljava/util/Map;Lcom/google/android/gms/maps/model/LatLngBounds;)V + public final fun getBoundingBox ()Lcom/google/android/gms/maps/model/LatLngBounds; + public final fun getLineStringStyle ()Lcom/google/maps/android/data/geojson/GeoJsonLineStringStyle; + public final fun getMarkerOptions ()Lcom/google/android/gms/maps/model/MarkerOptions; + public final fun getPointStyle ()Lcom/google/maps/android/data/geojson/GeoJsonPointStyle; + public final fun getPolygonOptions ()Lcom/google/android/gms/maps/model/PolygonOptions; + public final fun getPolygonStyle ()Lcom/google/maps/android/data/geojson/GeoJsonPolygonStyle; + public final fun getPolylineOptions ()Lcom/google/android/gms/maps/model/PolylineOptions; + public final fun setLineStringStyle (Lcom/google/maps/android/data/geojson/GeoJsonLineStringStyle;)V + public final fun setPointStyle (Lcom/google/maps/android/data/geojson/GeoJsonPointStyle;)V + public final fun setPolygonStyle (Lcom/google/maps/android/data/geojson/GeoJsonPolygonStyle;)V + public fun toString ()Ljava/lang/String; + public fun update (Ljava/util/Observable;Ljava/lang/Object;)V +} + +public final class com/google/maps/android/data/geojson/GeoJsonGeometryCollection : com/google/maps/android/data/MultiGeometry { + public fun (Ljava/util/List;)V + public final fun getGeometries ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonKt { + public static final fun geoJsonLayer (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; + public static final fun geoJsonLayer (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; + public static synthetic fun geoJsonLayer$default (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILjava/lang/Object;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; + public static synthetic fun geoJsonLayer$default (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILjava/lang/Object;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; +} + +public final class com/google/maps/android/data/geojson/GeoJsonLayer : com/google/maps/android/data/Layer { + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)V + public synthetic fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)V + public synthetic fun (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun addFeature (Lcom/google/maps/android/data/geojson/GeoJsonFeature;)V + public fun addLayerToMap ()V + public final fun getBoundingBox ()Lcom/google/android/gms/maps/model/LatLngBounds; + public fun getFeatures ()Ljava/lang/Iterable; + public fun getMap ()Lcom/google/android/gms/maps/GoogleMap; + public fun isLayerOnMap ()Z + public final fun removeFeature (Lcom/google/maps/android/data/geojson/GeoJsonFeature;)V + public fun removeLayerFromMap ()V + public fun setMap (Lcom/google/android/gms/maps/GoogleMap;)V + public fun setOnFeatureClickListener (Lcom/google/maps/android/data/Layer$OnFeatureClickListener;)V + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/geojson/GeoJsonLayer$GeoJsonOnFeatureClickListener : com/google/maps/android/data/Layer$OnFeatureClickListener { +} + +public final class com/google/maps/android/data/geojson/GeoJsonLineString : com/google/maps/android/data/LineString { + public fun (Ljava/util/List;)V + public fun (Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAltitudes ()Ljava/util/List; + public final fun getCoordinates ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonLineStringStyle : com/google/maps/android/data/Style, com/google/maps/android/data/geojson/GeoJsonStyle { + public static final field Companion Lcom/google/maps/android/data/geojson/GeoJsonLineStringStyle$Companion; + public fun ()V + public final fun getColor ()I + public final fun getEndCap ()Lcom/google/android/gms/maps/model/Cap; + public fun getGeometryType ()[Ljava/lang/String; + public final fun getPattern ()Ljava/util/List; + public final fun getStartCap ()Lcom/google/android/gms/maps/model/Cap; + public final fun getWidth ()F + public final fun getZIndex ()F + public final fun isClickable ()Z + public final fun isGeodesic ()Z + public fun isVisible ()Z + public final fun setClickable (Z)V + public final fun setColor (I)V + public final fun setEndCap (Lcom/google/android/gms/maps/model/Cap;)V + public final fun setGeodesic (Z)V + public final fun setPattern (Ljava/util/List;)V + public final fun setStartCap (Lcom/google/android/gms/maps/model/Cap;)V + public fun setVisible (Z)V + public final fun setWidth (F)V + public final fun setZIndex (F)V + public final fun toPolylineOptions ()Lcom/google/android/gms/maps/model/PolylineOptions; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonLineStringStyle$Companion { +} + +public final class com/google/maps/android/data/geojson/GeoJsonMultiLineString : com/google/maps/android/data/MultiGeometry { + public fun (Ljava/util/List;)V + public final fun getLineStrings ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonMultiPoint : com/google/maps/android/data/MultiGeometry { + public fun (Ljava/util/List;)V + public final fun getPoints ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonMultiPolygon : com/google/maps/android/data/MultiGeometry { + public fun (Ljava/util/List;)V + public final fun getPolygons ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonPoint : com/google/maps/android/data/Point { + public fun (Lcom/google/android/gms/maps/model/LatLng;)V + public fun (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/Double;)V + public synthetic fun (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAltitude ()Ljava/lang/Double; + public final fun getCoordinates ()Lcom/google/android/gms/maps/model/LatLng; + public final fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonPointStyle : com/google/maps/android/data/Style, com/google/maps/android/data/geojson/GeoJsonStyle { + public static final field Companion Lcom/google/maps/android/data/geojson/GeoJsonPointStyle$Companion; + public fun ()V + public final fun getAlpha ()F + public final fun getAnchorU ()F + public final fun getAnchorV ()F + public fun getGeometryType ()[Ljava/lang/String; + public final fun getIcon ()Lcom/google/android/gms/maps/model/BitmapDescriptor; + public final fun getInfoWindowAnchorU ()F + public final fun getInfoWindowAnchorV ()F + public fun getRotation ()F + public final fun getSnippet ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getZIndex ()F + public final fun isDraggable ()Z + public final fun isFlat ()Z + public fun isVisible ()Z + public final fun setAlpha (F)V + public final fun setAnchor (FF)V + public final fun setDraggable (Z)V + public final fun setFlat (Z)V + public final fun setIcon (Lcom/google/android/gms/maps/model/BitmapDescriptor;)V + public final fun setInfoWindowAnchor (FF)V + public final fun setRotation (F)V + public final fun setSnippet (Ljava/lang/String;)V + public final fun setTitle (Ljava/lang/String;)V + public fun setVisible (Z)V + public final fun setZIndex (F)V + public final fun toMarkerOptions ()Lcom/google/android/gms/maps/model/MarkerOptions; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonPointStyle$Companion { +} + +public final class com/google/maps/android/data/geojson/GeoJsonPolygon : com/google/maps/android/data/DataPolygon { + public static final field Companion Lcom/google/maps/android/data/geojson/GeoJsonPolygon$Companion; + public fun (Ljava/util/List;)V + public final fun getCoordinates ()Ljava/util/List; + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryObject ()Ljava/util/List; + public fun getGeometryType ()Ljava/lang/String; + public fun getInnerBoundaryCoordinates ()Ljava/util/List; + public fun getOuterBoundaryCoordinates ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonPolygon$Companion { +} + +public final class com/google/maps/android/data/geojson/GeoJsonPolygonStyle : com/google/maps/android/data/Style, com/google/maps/android/data/geojson/GeoJsonStyle { + public static final field Companion Lcom/google/maps/android/data/geojson/GeoJsonPolygonStyle$Companion; + public fun ()V + public final fun getFillColor ()I + public fun getGeometryType ()[Ljava/lang/String; + public final fun getStrokeColor ()I + public final fun getStrokeJointType ()I + public final fun getStrokePattern ()Ljava/util/List; + public final fun getStrokeWidth ()F + public final fun getZIndex ()F + public final fun isClickable ()Z + public final fun isGeodesic ()Z + public fun isVisible ()Z + public final fun setClickable (Z)V + public final fun setFillColor (I)V + public final fun setGeodesic (Z)V + public final fun setStrokeColor (I)V + public final fun setStrokeJointType (I)V + public final fun setStrokePattern (Ljava/util/List;)V + public final fun setStrokeWidth (F)V + public fun setVisible (Z)V + public final fun setZIndex (F)V + public final fun toPolygonOptions ()Lcom/google/android/gms/maps/model/PolygonOptions; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/geojson/GeoJsonPolygonStyle$Companion { +} + +public abstract interface class com/google/maps/android/data/geojson/GeoJsonStyle { + public abstract fun getGeometryType ()[Ljava/lang/String; + public abstract fun isVisible ()Z + public abstract fun setVisible (Z)V +} + +public class com/google/maps/android/data/kml/DefaultKmlUrlSanitizer : com/google/maps/android/data/kml/KmlUrlSanitizer { + public fun ()V + public static fun isInternalAddress (Ljava/net/InetAddress;)Z + public fun sanitizeUrl (Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlContainer { + public fun (Ljava/util/HashMap;Ljava/util/HashMap;Ljava/util/HashMap;Ljava/util/HashMap;Ljava/util/ArrayList;Ljava/util/HashMap;Ljava/lang/String;)V + public final fun getContainerId ()Ljava/lang/String; + public final fun getContainers ()Ljava/lang/Iterable; + public final fun getGroundOverlays ()Ljava/lang/Iterable; + public final fun getPlacemarks ()Ljava/lang/Iterable; + public final fun getProperties ()Ljava/lang/Iterable; + public final fun getProperty (Ljava/lang/String;)Ljava/lang/String; + public final fun getStyle (Ljava/lang/String;)Lcom/google/maps/android/data/kml/KmlStyle; + public final fun getStyleIdFromMap (Ljava/lang/String;)Ljava/lang/String; + public final fun hasContainers ()Z + public final fun hasPlacemarks ()Z + public final fun hasProperties ()Z + public final fun hasProperty (Ljava/lang/String;)Z + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlGroundOverlay { + public fun (Ljava/lang/String;Lcom/google/android/gms/maps/model/LatLngBounds;FILjava/util/Map;F)V + public final fun getImageUrl ()Ljava/lang/String; + public final fun getLatLngBox ()Lcom/google/android/gms/maps/model/LatLngBounds; + public final fun getProperties ()Ljava/lang/Iterable; + public final fun getProperty (Ljava/lang/String;)Ljava/lang/String; + public final fun hasProperty (Ljava/lang/String;)Z + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlKt { + public static final fun kmlLayer (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;)Lcom/google/maps/android/data/kml/KmlLayer; + public static final fun kmlLayer (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;)Lcom/google/maps/android/data/kml/KmlLayer; + public static synthetic fun kmlLayer$default (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;ILjava/lang/Object;)Lcom/google/maps/android/data/kml/KmlLayer; + public static synthetic fun kmlLayer$default (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;ILjava/lang/Object;)Lcom/google/maps/android/data/kml/KmlLayer; +} + +public final class com/google/maps/android/data/kml/KmlLayer : com/google/maps/android/data/Layer { + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;I)V + public fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;IJ)V + public synthetic fun (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;IJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;I)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;IJ)V + public synthetic fun (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;IJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun addLayerToMap ()V + public final fun getAllGroundOverlays ()Ljava/util/List; + public final fun getAllPlacemarks ()Ljava/util/List; + public final fun getContainers ()Ljava/lang/Iterable; + public fun getFeatures ()Ljava/lang/Iterable; + public final fun getGroundOverlays ()Ljava/lang/Iterable; + public fun getMap ()Lcom/google/android/gms/maps/GoogleMap; + public final fun getPlacemarks ()Ljava/lang/Iterable; + public final fun hasContainers ()Z + public final fun hasPlacemarks ()Z + public fun isLayerOnMap ()Z + public fun removeLayerFromMap ()V + public fun setMap (Lcom/google/android/gms/maps/GoogleMap;)V + public fun setOnFeatureClickListener (Lcom/google/maps/android/data/Layer$OnFeatureClickListener;)V +} + +public final class com/google/maps/android/data/kml/KmlLineString : com/google/maps/android/data/LineString { + public fun (Ljava/util/ArrayList;)V + public fun (Ljava/util/ArrayList;Ljava/util/ArrayList;)V + public synthetic fun (Ljava/util/ArrayList;Ljava/util/ArrayList;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAltitudes ()Ljava/util/ArrayList; + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryObject ()Ljava/util/ArrayList; + public synthetic fun getGeometryObject ()Ljava/util/List; +} + +public final class com/google/maps/android/data/kml/KmlMultiGeometry : com/google/maps/android/data/MultiGeometry { + public fun (Ljava/util/ArrayList;)V + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryObject ()Ljava/util/ArrayList; + public synthetic fun getGeometryObject ()Ljava/util/List; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlPlacemark : com/google/maps/android/data/Feature { + public fun (Lcom/google/maps/android/data/Geometry;Ljava/lang/String;Lcom/google/maps/android/data/kml/KmlStyle;Ljava/util/Map;)V + public final fun getInlineStyle ()Lcom/google/maps/android/data/kml/KmlStyle; + public final fun getMarkerOptions ()Lcom/google/android/gms/maps/model/MarkerOptions; + public final fun getPolygonOptions ()Lcom/google/android/gms/maps/model/PolygonOptions; + public final fun getPolylineOptions ()Lcom/google/android/gms/maps/model/PolylineOptions; + public final fun getStyleId ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlPoint : com/google/maps/android/data/Point { + public fun (Lcom/google/android/gms/maps/model/LatLng;)V + public fun (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/Double;)V + public synthetic fun (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAltitude ()Ljava/lang/Double; +} + +public final class com/google/maps/android/data/kml/KmlPolygon : com/google/maps/android/data/DataPolygon { + public static final field Companion Lcom/google/maps/android/data/kml/KmlPolygon$Companion; + public static final field GEOMETRY_TYPE Ljava/lang/String; + public fun (Ljava/util/List;Ljava/util/List;)V + public synthetic fun getGeometryObject ()Ljava/lang/Object; + public fun getGeometryObject ()Ljava/util/List; + public fun getGeometryType ()Ljava/lang/String; + public fun getInnerBoundaryCoordinates ()Ljava/util/List; + public fun getOuterBoundaryCoordinates ()Ljava/util/List; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlPolygon$Companion { +} + +public final class com/google/maps/android/data/kml/KmlStyle : com/google/maps/android/data/Style { + public static final field Companion Lcom/google/maps/android/data/kml/KmlStyle$Companion; + public fun ()V + public final fun getBalloonOptions ()Ljava/util/HashMap; + public final fun getIconScale ()D + public final fun getIconUrl ()Ljava/lang/String; + public final fun getMarkerOptions ()Lcom/google/android/gms/maps/model/MarkerOptions; + public final fun getPolygonOptions ()Lcom/google/android/gms/maps/model/PolygonOptions; + public final fun getPolylineOptions ()Lcom/google/android/gms/maps/model/PolylineOptions; + public final fun getStyleId ()Ljava/lang/String; + public final fun hasBalloonStyle ()Z + public final fun hasFill ()Z + public final fun hasOutline ()Z + public final fun isIconRandomColorMode ()Z + public final fun isLineRandomColorMode ()Z + public final fun isPolyRandomColorMode ()Z + public final fun isStyleSet (Ljava/lang/String;)Z + public final fun setFill (Z)V + public final fun setFillColor (Ljava/lang/String;)V + public final fun setHeading (F)V + public final fun setHotSpot (FFLjava/lang/String;Ljava/lang/String;)V + public final fun setIconColorMode (Ljava/lang/String;)V + public final fun setIconScale (D)V + public final fun setIconUrl (Ljava/lang/String;)V + public final fun setInfoWindowText (Ljava/lang/String;)V + public final fun setLineColorMode (Ljava/lang/String;)V + public final fun setMarkerColor (Ljava/lang/String;)V + public final fun setOutline (Z)V + public final fun setOutlineColor (Ljava/lang/String;)V + public final fun setPolyColorMode (Ljava/lang/String;)V + public final fun setStyleId (Ljava/lang/String;)V + public final fun setWidth (F)V + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/kml/KmlStyle$Companion { + public final fun computeRandomColor (I)I +} + +public abstract interface class com/google/maps/android/data/kml/KmlUrlSanitizer { + public abstract fun sanitizeUrl (Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/Feature { + public fun (Lcom/google/maps/android/data/parser/Geometry;Ljava/util/Map;Lcom/google/maps/android/data/parser/kml/Style;)V + public synthetic fun (Lcom/google/maps/android/data/parser/Geometry;Ljava/util/Map;Lcom/google/maps/android/data/parser/kml/Style;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/Geometry; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Lcom/google/maps/android/data/parser/kml/Style; + public final fun copy (Lcom/google/maps/android/data/parser/Geometry;Ljava/util/Map;Lcom/google/maps/android/data/parser/kml/Style;)Lcom/google/maps/android/data/parser/Feature; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/Feature;Lcom/google/maps/android/data/parser/Geometry;Ljava/util/Map;Lcom/google/maps/android/data/parser/kml/Style;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/Feature; + public fun equals (Ljava/lang/Object;)Z + public final fun getGeometry ()Lcom/google/maps/android/data/parser/Geometry; + public final fun getProperties ()Ljava/util/Map; + public final fun getStyle ()Lcom/google/maps/android/data/parser/kml/Style; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/GeoData { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/GeoData; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/GeoData;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/GeoData; + public fun equals (Ljava/lang/Object;)Z + public final fun getFeatures ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/parser/GeoFileParser { +} + +public abstract interface class com/google/maps/android/data/parser/Geometry { +} + +public final class com/google/maps/android/data/parser/Geometry$GeometryCollection : com/google/maps/android/data/parser/Geometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/Geometry$GeometryCollection; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/Geometry$GeometryCollection;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/Geometry$GeometryCollection; + public fun equals (Ljava/lang/Object;)Z + public final fun getGeometries ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/Geometry$LineString : com/google/maps/android/data/parser/Geometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/Geometry$LineString; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/Geometry$LineString;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/Geometry$LineString; + public fun equals (Ljava/lang/Object;)Z + public final fun getPoints ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/Geometry$Point : com/google/maps/android/data/parser/Geometry { + public fun (DDLjava/lang/Double;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()Ljava/lang/Double; + public final fun copy (DDLjava/lang/Double;)Lcom/google/maps/android/data/parser/Geometry$Point; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/Geometry$Point;DDLjava/lang/Double;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/Geometry$Point; + public fun equals (Ljava/lang/Object;)Z + public final fun getAlt ()Ljava/lang/Double; + public final fun getLat ()D + public final fun getLon ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/Geometry$Polygon : com/google/maps/android/data/parser/Geometry { + public fun (Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/google/maps/android/data/parser/Geometry$Polygon; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/Geometry$Polygon;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/Geometry$Polygon; + public fun equals (Ljava/lang/Object;)Z + public final fun getHoles ()Ljava/util/List; + public final fun getShell ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/Style { + public fun (Ljava/lang/String;Ljava/lang/Float;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Float; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/Float;Ljava/lang/String;)Lcom/google/maps/android/data/parser/Style; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/Style;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/Style; + public fun equals (Ljava/lang/Object;)Z + public final fun getFillColor ()Ljava/lang/String; + public final fun getStrokeColor ()Ljava/lang/String; + public final fun getStrokeWidth ()Ljava/lang/Float; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/Coordinates { + public fun (DDLjava/lang/Double;)V + public synthetic fun (DDLjava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()Ljava/lang/Double; + public final fun copy (DDLjava/lang/Double;)Lcom/google/maps/android/data/parser/geojson/Coordinates; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/Coordinates;DDLjava/lang/Double;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/Coordinates; + public fun equals (Ljava/lang/Object;)Z + public final fun getAlt ()Ljava/lang/Double; + public final fun getLat ()D + public final fun getLng ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonFeature : com/google/maps/android/data/parser/geojson/GeoJsonObject { + public fun (Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometry;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometry;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometry; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometry;Ljava/util/Map;Ljava/lang/String;)Lcom/google/maps/android/data/parser/geojson/GeoJsonFeature; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonFeature;Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometry;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonFeature; + public fun equals (Ljava/lang/Object;)Z + public final fun getGeometry ()Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometry; + public final fun getId ()Ljava/lang/String; + public final fun getProperties ()Ljava/util/Map; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonFeatureCollection : com/google/maps/android/data/parser/geojson/GeoJsonObject { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonFeatureCollection; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonFeatureCollection;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonFeatureCollection; + public fun equals (Ljava/lang/Object;)Z + public final fun getFeatures ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/parser/geojson/GeoJsonGeometry : com/google/maps/android/data/parser/geojson/GeoJsonObject { +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonGeometryCollection : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometryCollection; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometryCollection;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonGeometryCollection; + public fun equals (Ljava/lang/Object;)Z + public final fun getGeometries ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonLineString : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonLineString; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonLineString;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonLineString; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonMultiLineString : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiLineString; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiLineString;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiLineString; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonMultiPoint : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiPoint; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiPoint;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiPoint; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonMultiPolygon : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiPolygon; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiPolygon;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonMultiPolygon; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/parser/geojson/GeoJsonObject { + public abstract fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonParser { + public static final field Companion Lcom/google/maps/android/data/parser/geojson/GeoJsonParser$Companion; + public static final field DEFAULT_MAX_INPUT_SIZE J + public static final field DEFAULT_MAX_STRUCTURAL_DEPTH I + public static final field MAX_GEOMETRY_DEPTH I + public fun ()V + public fun (J)V + public fun (JI)V + public synthetic fun (JIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun parse (Ljava/io/InputStream;)Lcom/google/maps/android/data/parser/geojson/GeoJsonObject; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonParser$Companion { + public final fun canParse (Ljava/lang/String;)Z + public final fun getSUPPORTED_EXTENSIONS ()Ljava/util/Set; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonPoint : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Lcom/google/maps/android/data/parser/geojson/Coordinates;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/geojson/Coordinates; + public final fun copy (Lcom/google/maps/android/data/parser/geojson/Coordinates;)Lcom/google/maps/android/data/parser/geojson/GeoJsonPoint; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonPoint;Lcom/google/maps/android/data/parser/geojson/Coordinates;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonPoint; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Lcom/google/maps/android/data/parser/geojson/Coordinates; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/geojson/GeoJsonPolygon : com/google/maps/android/data/parser/geojson/GeoJsonGeometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/geojson/GeoJsonPolygon; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/geojson/GeoJsonPolygon;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/geojson/GeoJsonPolygon; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/gpx/Gpx { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/Gpx$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/gpx/Metadata;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/gpx/Metadata;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lcom/google/maps/android/data/parser/gpx/Metadata; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/gpx/Metadata;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lcom/google/maps/android/data/parser/gpx/Gpx; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/gpx/Gpx;Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/gpx/Metadata;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/gpx/Gpx; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreator ()Ljava/lang/String; + public final fun getMetadata ()Lcom/google/maps/android/data/parser/gpx/Metadata; + public final fun getRoutes ()Ljava/util/List; + public final fun getTracks ()Ljava/util/List; + public final fun getVersion ()Ljava/lang/String; + public final fun getWaypoints ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/gpx/Gpx$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/gpx/Gpx$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/gpx/Gpx; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/gpx/Gpx;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Gpx$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/GpxParser { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/GpxParser$Companion; + public fun ()V + public final fun parse (Ljava/io/InputStream;)Lcom/google/maps/android/data/parser/gpx/Gpx; +} + +public final class com/google/maps/android/data/parser/gpx/GpxParser$Companion { + public final fun canParse (Ljava/lang/String;)Z + public final fun getSUPPORTED_EXTENSIONS ()Ljava/util/Set; +} + +public final class com/google/maps/android/data/parser/gpx/Metadata { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/Metadata$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/google/maps/android/data/parser/gpx/Metadata; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/gpx/Metadata;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/gpx/Metadata; + public fun equals (Ljava/lang/Object;)Z + public final fun getDesc ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getTime ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/gpx/Metadata$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/gpx/Metadata$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/gpx/Metadata; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/gpx/Metadata;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Metadata$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Rte { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/Rte$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lcom/google/maps/android/data/parser/gpx/Rte; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/gpx/Rte;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/gpx/Rte; + public fun equals (Ljava/lang/Object;)Z + public final fun getDesc ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getRoutePoints ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/gpx/Rte$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/gpx/Rte$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/gpx/Rte; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/gpx/Rte;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Rte$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Trk { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/Trk$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lcom/google/maps/android/data/parser/gpx/Trk; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/gpx/Trk;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/gpx/Trk; + public fun equals (Ljava/lang/Object;)Z + public final fun getDesc ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getTrackSegments ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/gpx/Trk$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/gpx/Trk$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/gpx/Trk; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/gpx/Trk;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Trk$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/TrkSeg { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/TrkSeg$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/gpx/TrkSeg; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/gpx/TrkSeg;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/gpx/TrkSeg; + public fun equals (Ljava/lang/Object;)Z + public final fun getTrackPoints ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/gpx/TrkSeg$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/gpx/TrkSeg$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/gpx/TrkSeg; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/gpx/TrkSeg;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/TrkSeg$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Wpt { + public static final field Companion Lcom/google/maps/android/data/parser/gpx/Wpt$Companion; + public fun (DDLjava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (DDLjava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()Ljava/lang/Double; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun copy (DDLjava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/google/maps/android/data/parser/gpx/Wpt; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/gpx/Wpt;DDLjava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/gpx/Wpt; + public fun equals (Ljava/lang/Object;)Z + public final fun getDesc ()Ljava/lang/String; + public final fun getEle ()Ljava/lang/Double; + public final fun getElevation ()Ljava/lang/Double; + public final fun getLat ()D + public final fun getLon ()D + public final fun getName ()Ljava/lang/String; + public final fun getSym ()Ljava/lang/String; + public final fun getTime ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/gpx/Wpt$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/gpx/Wpt$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/gpx/Wpt; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/gpx/Wpt;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/gpx/Wpt$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/AltitudeMode : java/lang/Enum { + public static final field ABSOLUTE Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public static final field CLAMP_TO_GROUND Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public static final field CLAMP_TO_SEA_FLOOR Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public static final field Companion Lcom/google/maps/android/data/parser/kml/AltitudeMode$Companion; + public static final field RELATIVE_TO_GROUND Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public static final field RELATIVE_TO_SEA_FLOOR Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public static fun values ()[Lcom/google/maps/android/data/parser/kml/AltitudeMode; +} + +public final class com/google/maps/android/data/parser/kml/AltitudeMode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/AndroidImageDecoder : com/google/maps/android/data/parser/kml/ImageDecoder { + public fun ()V + public fun decode ([B)Landroid/graphics/Bitmap; +} + +public final class com/google/maps/android/data/parser/kml/BalloonVisibility { + public static final field Companion Lcom/google/maps/android/data/parser/kml/BalloonVisibility$Companion; + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun copy (I)Lcom/google/maps/android/data/parser/kml/BalloonVisibility; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/BalloonVisibility;IILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/BalloonVisibility; + public fun equals (Ljava/lang/Object;)Z + public final fun getValue ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/BalloonVisibility$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/BalloonVisibility$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/BalloonVisibility; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/BalloonVisibility;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/BalloonVisibility$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Boundary { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Boundary$Companion; + public fun (Lcom/google/maps/android/data/parser/kml/LinearRing;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/kml/LinearRing; + public final fun copy (Lcom/google/maps/android/data/parser/kml/LinearRing;)Lcom/google/maps/android/data/parser/kml/Boundary; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Boundary;Lcom/google/maps/android/data/parser/kml/LinearRing;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Boundary; + public fun equals (Ljava/lang/Object;)Z + public final fun getLinearRing ()Lcom/google/maps/android/data/parser/kml/LinearRing; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Boundary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Boundary$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Boundary; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Boundary;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Boundary$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Data { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Data$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/google/maps/android/data/parser/kml/Data; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Data;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Data; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Data;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Document : com/google/maps/android/data/parser/kml/KmlFeature { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Document$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/Document; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Document;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Document; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getFolders ()Ljava/util/List; + public final fun getGroundOverlays ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getPlacemarks ()Ljava/util/List; + public final fun getStyleMaps ()Ljava/util/List; + public final fun getStyles ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Document$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Document$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Document; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Document;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Document$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/ExtendedData { + public static final field Companion Lcom/google/maps/android/data/parser/kml/ExtendedData$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/ExtendedData; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/ExtendedData;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/ExtendedData; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/ExtendedData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/ExtendedData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/ExtendedData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/ExtendedData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/ExtendedData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Folder : com/google/maps/android/data/parser/kml/KmlFeature { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Folder$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/Folder; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Folder;Ljava/lang/String;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Folder; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getFolders ()Ljava/util/List; + public final fun getGroundOverlays ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getPlacemarks ()Ljava/util/List; + public final fun getVisibility ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Folder$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Folder$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Folder; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Folder;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Folder$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/GroundOverlay { + public static final field Companion Lcom/google/maps/android/data/parser/kml/GroundOverlay$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;ZLjava/lang/Integer;Lcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/LatLonBox;Lcom/google/maps/android/data/parser/kml/LatLonQuad;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ZLjava/lang/Integer;Lcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/LatLonBox;Lcom/google/maps/android/data/parser/kml/LatLonQuad;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Z + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Lcom/google/maps/android/data/parser/kml/Icon; + public final fun component6 ()Lcom/google/maps/android/data/parser/kml/LatLonBox; + public final fun component7 ()Lcom/google/maps/android/data/parser/kml/LatLonQuad; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;ZLjava/lang/Integer;Lcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/LatLonBox;Lcom/google/maps/android/data/parser/kml/LatLonQuad;)Lcom/google/maps/android/data/parser/kml/GroundOverlay; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/GroundOverlay;Ljava/lang/String;Ljava/lang/Integer;ZLjava/lang/Integer;Lcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/LatLonBox;Lcom/google/maps/android/data/parser/kml/LatLonQuad;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/GroundOverlay; + public fun equals (Ljava/lang/Object;)Z + public final fun getColor ()Ljava/lang/Integer; + public final fun getDrawOrder ()Ljava/lang/Integer; + public final fun getIcon ()Lcom/google/maps/android/data/parser/kml/Icon; + public final fun getLatLonBox ()Lcom/google/maps/android/data/parser/kml/LatLonBox; + public final fun getLatLonQuad ()Lcom/google/maps/android/data/parser/kml/LatLonQuad; + public final fun getName ()Ljava/lang/String; + public final fun getVisibility ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/GroundOverlay$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/GroundOverlay$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/GroundOverlay; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/GroundOverlay;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/GroundOverlay$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/HotSpot { + public static final field Companion Lcom/google/maps/android/data/parser/kml/HotSpot$Companion; + public fun ()V + public fun (DDLjava/lang/String;Ljava/lang/String;)V + public synthetic fun (DDLjava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (DDLjava/lang/String;Ljava/lang/String;)Lcom/google/maps/android/data/parser/kml/HotSpot; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/HotSpot;DDLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/HotSpot; + public fun equals (Ljava/lang/Object;)Z + public final fun getX ()D + public final fun getXunits ()Ljava/lang/String; + public final fun getY ()D + public final fun getYunits ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/HotSpot$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/HotSpot$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/HotSpot; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/HotSpot;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/HotSpot$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Icon { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Icon$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/google/maps/android/data/parser/kml/Icon; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Icon;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Icon; + public fun equals (Ljava/lang/Object;)Z + public final fun getHref ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Icon$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Icon$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Icon; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Icon;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Icon$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/IconStyle { + public static final field Companion Lcom/google/maps/android/data/parser/kml/IconStyle$Companion; + public fun ()V + public fun (FLcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/HotSpot;)V + public synthetic fun (FLcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/HotSpot;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()F + public final fun component2 ()Lcom/google/maps/android/data/parser/kml/Icon; + public final fun component3 ()Lcom/google/maps/android/data/parser/kml/HotSpot; + public final fun copy (FLcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/HotSpot;)Lcom/google/maps/android/data/parser/kml/IconStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/IconStyle;FLcom/google/maps/android/data/parser/kml/Icon;Lcom/google/maps/android/data/parser/kml/HotSpot;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/IconStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getHotSpot ()Lcom/google/maps/android/data/parser/kml/HotSpot; + public final fun getIcon ()Lcom/google/maps/android/data/parser/kml/Icon; + public final fun getScale ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/IconStyle$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/IconStyle$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/IconStyle; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/IconStyle;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/IconStyle$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class com/google/maps/android/data/parser/kml/ImageDecoder { + public abstract fun decode ([B)Landroid/graphics/Bitmap; +} + +public final class com/google/maps/android/data/parser/kml/Kml { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Kml$Companion; + public fun ()V + public fun (Lcom/google/maps/android/data/parser/kml/Document;Lcom/google/maps/android/data/parser/kml/Folder;Lcom/google/maps/android/data/parser/kml/Placemark;Lcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/StyleMap;Lcom/google/maps/android/data/parser/kml/GroundOverlay;Ljava/util/Map;)V + public synthetic fun (Lcom/google/maps/android/data/parser/kml/Document;Lcom/google/maps/android/data/parser/kml/Folder;Lcom/google/maps/android/data/parser/kml/Placemark;Lcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/StyleMap;Lcom/google/maps/android/data/parser/kml/GroundOverlay;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/kml/Document; + public final fun component2 ()Lcom/google/maps/android/data/parser/kml/Folder; + public final fun component3 ()Lcom/google/maps/android/data/parser/kml/Placemark; + public final fun component4 ()Lcom/google/maps/android/data/parser/kml/Style; + public final fun component5 ()Lcom/google/maps/android/data/parser/kml/StyleMap; + public final fun component6 ()Lcom/google/maps/android/data/parser/kml/GroundOverlay; + public final fun component7 ()Ljava/util/Map; + public final fun copy (Lcom/google/maps/android/data/parser/kml/Document;Lcom/google/maps/android/data/parser/kml/Folder;Lcom/google/maps/android/data/parser/kml/Placemark;Lcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/StyleMap;Lcom/google/maps/android/data/parser/kml/GroundOverlay;Ljava/util/Map;)Lcom/google/maps/android/data/parser/kml/Kml; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Kml;Lcom/google/maps/android/data/parser/kml/Document;Lcom/google/maps/android/data/parser/kml/Folder;Lcom/google/maps/android/data/parser/kml/Placemark;Lcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/StyleMap;Lcom/google/maps/android/data/parser/kml/GroundOverlay;Ljava/util/Map;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Kml; + public fun equals (Ljava/lang/Object;)Z + public final fun getDocument ()Lcom/google/maps/android/data/parser/kml/Document; + public final fun getFolder ()Lcom/google/maps/android/data/parser/kml/Folder; + public final fun getGroundOverlay ()Lcom/google/maps/android/data/parser/kml/GroundOverlay; + public final fun getImages ()Ljava/util/Map; + public final fun getPlacemark ()Lcom/google/maps/android/data/parser/kml/Placemark; + public final fun getStyle ()Lcom/google/maps/android/data/parser/kml/Style; + public final fun getStyleMap ()Lcom/google/maps/android/data/parser/kml/StyleMap; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Kml$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Kml$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Kml; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Kml;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Kml$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class com/google/maps/android/data/parser/kml/KmlFeature { +} + +public final class com/google/maps/android/data/parser/kml/KmlModelKt { + public static final fun findByPlacemarksById (Lcom/google/maps/android/data/parser/kml/Kml;Ljava/lang/String;)Ljava/util/List; +} + +public final class com/google/maps/android/data/parser/kml/KmlParser { + public static final field Companion Lcom/google/maps/android/data/parser/kml/KmlParser$Companion; + public static final field MAX_ELEMENT_DEPTH I + public fun ()V + public final fun parse (Ljava/io/InputStream;)Lcom/google/maps/android/data/parser/kml/Kml; + public final fun parseAsKml (Ljava/io/InputStream;)Lcom/google/maps/android/data/parser/kml/Kml; +} + +public final class com/google/maps/android/data/parser/kml/KmlParser$Companion { + public final fun canParse (Ljava/lang/String;)Z + public final fun getSUPPORTED_EXTENSIONS ()Ljava/util/Set; +} + +public final class com/google/maps/android/data/parser/kml/KmzParser { + public static final field Companion Lcom/google/maps/android/data/parser/kml/KmzParser$Companion; + public fun ()V + public fun (Lcom/google/maps/android/data/parser/kml/ImageDecoder;)V + public fun (Lcom/google/maps/android/data/parser/kml/ImageDecoder;I)V + public fun (Lcom/google/maps/android/data/parser/kml/ImageDecoder;IJ)V + public synthetic fun (Lcom/google/maps/android/data/parser/kml/ImageDecoder;IJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun parse (Ljava/io/InputStream;)Lcom/google/maps/android/data/parser/kml/Kml; +} + +public final class com/google/maps/android/data/parser/kml/KmzParser$Companion { + public final fun canParse (Ljava/lang/String;)Z + public final fun getSUPPORTED_EXTENSIONS ()Ljava/util/Set; +} + +public final class com/google/maps/android/data/parser/kml/LabelStyle { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LabelStyle$Companion; + public fun ()V + public fun (FLjava/lang/Integer;)V + public synthetic fun (FLjava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()F + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (FLjava/lang/Integer;)Lcom/google/maps/android/data/parser/kml/LabelStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LabelStyle;FLjava/lang/Integer;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LabelStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getColor ()Ljava/lang/Integer; + public final fun getScale ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/LabelStyle$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/LabelStyle$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/LabelStyle; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/LabelStyle;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LabelStyle$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LatLngAlt { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LatLngAlt$Companion; + public fun (DDLjava/lang/Double;)V + public synthetic fun (DDLjava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()Ljava/lang/Double; + public final fun copy (DDLjava/lang/Double;)Lcom/google/maps/android/data/parser/kml/LatLngAlt; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LatLngAlt;DDLjava/lang/Double;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LatLngAlt; + public fun equals (Ljava/lang/Object;)Z + public final fun getAltitude ()Ljava/lang/Double; + public final fun getLatitude ()D + public final fun getLongitude ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/parser/kml/LatLngAlt$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LatLonBox { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LatLonBox$Companion; + public fun (DDDDLjava/lang/Double;)V + public synthetic fun (DDDDLjava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()D + public final fun component4 ()D + public final fun component5 ()Ljava/lang/Double; + public final fun copy (DDDDLjava/lang/Double;)Lcom/google/maps/android/data/parser/kml/LatLonBox; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LatLonBox;DDDDLjava/lang/Double;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LatLonBox; + public fun equals (Ljava/lang/Object;)Z + public final fun getEast ()D + public final fun getNorth ()D + public final fun getRotation ()Ljava/lang/Double; + public final fun getSouth ()D + public final fun getWest ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/LatLonBox$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/LatLonBox$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/LatLonBox; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/LatLonBox;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LatLonBox$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LatLonQuad { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LatLonQuad$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/LatLonQuad; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LatLonQuad;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LatLonQuad; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/LatLonQuad$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/LatLonQuad$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/LatLonQuad; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/LatLonQuad;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LatLonQuad$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LineString { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LineString$Companion; + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/LineString; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LineString;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LineString; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/LineString$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/LineString$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/LineString; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/LineString;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LineString$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LineStyle { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LineStyle$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/Float;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Float;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/Float; + public final fun copy (Ljava/lang/Integer;Ljava/lang/Float;)Lcom/google/maps/android/data/parser/kml/LineStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LineStyle;Ljava/lang/Integer;Ljava/lang/Float;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LineStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getColor ()Ljava/lang/Integer; + public final fun getWidth ()Ljava/lang/Float; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/LineStyle$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/LineStyle$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/LineStyle; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/LineStyle;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LineStyle$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LinearRing { + public static final field Companion Lcom/google/maps/android/data/parser/kml/LinearRing$Companion; + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/LinearRing; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/LinearRing;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/LinearRing; + public fun equals (Ljava/lang/Object;)Z + public final fun getCoordinates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/LinearRing$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/LinearRing$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/LinearRing; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/LinearRing;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/LinearRing$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/MultiGeometry { + public static final field Companion Lcom/google/maps/android/data/parser/kml/MultiGeometry$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/MultiGeometry; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/MultiGeometry;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/MultiGeometry; + public fun equals (Ljava/lang/Object;)Z + public final fun getLineStrings ()Ljava/util/List; + public final fun getMultiGeometries ()Ljava/util/List; + public final fun getPoints ()Ljava/util/List; + public final fun getPolygons ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/MultiGeometry$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/MultiGeometry$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/MultiGeometry; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/MultiGeometry;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/MultiGeometry$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/MultiTrack { + public static final field Companion Lcom/google/maps/android/data/parser/kml/MultiTrack$Companion; + public fun ()V + public fun (Lcom/google/maps/android/data/parser/kml/AltitudeMode;Ljava/util/List;)V + public synthetic fun (Lcom/google/maps/android/data/parser/kml/AltitudeMode;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lcom/google/maps/android/data/parser/kml/AltitudeMode;Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/MultiTrack; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/MultiTrack;Lcom/google/maps/android/data/parser/kml/AltitudeMode;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/MultiTrack; + public fun equals (Ljava/lang/Object;)Z + public final fun getAltitudeMode ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun getTracks ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/MultiTrack$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/MultiTrack$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/MultiTrack; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/MultiTrack;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/MultiTrack$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Pair { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Pair$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/google/maps/android/data/parser/kml/Pair; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Pair;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Pair; + public fun equals (Ljava/lang/Object;)Z + public final fun getKey ()Ljava/lang/String; + public final fun getStyleUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Pair$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Pair$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Pair; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Pair;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Pair$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Placemark : com/google/maps/android/data/parser/kml/KmlFeature { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Placemark$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/Point;Lcom/google/maps/android/data/parser/kml/LineString;Lcom/google/maps/android/data/parser/kml/Polygon;Lcom/google/maps/android/data/parser/kml/MultiGeometry;Lcom/google/maps/android/data/parser/kml/Track;Lcom/google/maps/android/data/parser/kml/MultiTrack;ILcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/ExtendedData;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/Point;Lcom/google/maps/android/data/parser/kml/LineString;Lcom/google/maps/android/data/parser/kml/Polygon;Lcom/google/maps/android/data/parser/kml/MultiGeometry;Lcom/google/maps/android/data/parser/kml/Track;Lcom/google/maps/android/data/parser/kml/MultiTrack;ILcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/ExtendedData;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Lcom/google/maps/android/data/parser/kml/MultiTrack; + public final fun component11 ()I + public final fun component12 ()Lcom/google/maps/android/data/parser/kml/Style; + public final fun component13 ()Lcom/google/maps/android/data/parser/kml/ExtendedData; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lcom/google/maps/android/data/parser/kml/Point; + public final fun component6 ()Lcom/google/maps/android/data/parser/kml/LineString; + public final fun component7 ()Lcom/google/maps/android/data/parser/kml/Polygon; + public final fun component8 ()Lcom/google/maps/android/data/parser/kml/MultiGeometry; + public final fun component9 ()Lcom/google/maps/android/data/parser/kml/Track; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/Point;Lcom/google/maps/android/data/parser/kml/LineString;Lcom/google/maps/android/data/parser/kml/Polygon;Lcom/google/maps/android/data/parser/kml/MultiGeometry;Lcom/google/maps/android/data/parser/kml/Track;Lcom/google/maps/android/data/parser/kml/MultiTrack;ILcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/ExtendedData;)Lcom/google/maps/android/data/parser/kml/Placemark; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Placemark;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/Point;Lcom/google/maps/android/data/parser/kml/LineString;Lcom/google/maps/android/data/parser/kml/Polygon;Lcom/google/maps/android/data/parser/kml/MultiGeometry;Lcom/google/maps/android/data/parser/kml/Track;Lcom/google/maps/android/data/parser/kml/MultiTrack;ILcom/google/maps/android/data/parser/kml/Style;Lcom/google/maps/android/data/parser/kml/ExtendedData;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Placemark; + public fun equals (Ljava/lang/Object;)Z + public final fun getBalloonVisibility ()I + public final fun getDescription ()Ljava/lang/String; + public final fun getExtendedData ()Lcom/google/maps/android/data/parser/kml/ExtendedData; + public final fun getId ()Ljava/lang/String; + public final fun getLineString ()Lcom/google/maps/android/data/parser/kml/LineString; + public final fun getMultiGeometry ()Lcom/google/maps/android/data/parser/kml/MultiGeometry; + public final fun getMultiTrack ()Lcom/google/maps/android/data/parser/kml/MultiTrack; + public final fun getName ()Ljava/lang/String; + public final fun getPoint ()Lcom/google/maps/android/data/parser/kml/Point; + public final fun getPolygon ()Lcom/google/maps/android/data/parser/kml/Polygon; + public final fun getStyle ()Lcom/google/maps/android/data/parser/kml/Style; + public final fun getStyleUrl ()Ljava/lang/String; + public final fun getTrack ()Lcom/google/maps/android/data/parser/kml/Track; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Placemark$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Placemark$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Placemark; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Placemark;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Placemark$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Point { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Point$Companion; + public fun (Lcom/google/maps/android/data/parser/kml/LatLngAlt;Lcom/google/maps/android/data/parser/kml/AltitudeMode;)V + public synthetic fun (Lcom/google/maps/android/data/parser/kml/LatLngAlt;Lcom/google/maps/android/data/parser/kml/AltitudeMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/kml/LatLngAlt; + public final fun component2 ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun copy (Lcom/google/maps/android/data/parser/kml/LatLngAlt;Lcom/google/maps/android/data/parser/kml/AltitudeMode;)Lcom/google/maps/android/data/parser/kml/Point; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Point;Lcom/google/maps/android/data/parser/kml/LatLngAlt;Lcom/google/maps/android/data/parser/kml/AltitudeMode;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Point; + public fun equals (Ljava/lang/Object;)Z + public final fun getAltitudeMode ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun getCoordinates ()Lcom/google/maps/android/data/parser/kml/LatLngAlt; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Point$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Point$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Point; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Point;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Point$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/PolyStyle { + public static final field Companion Lcom/google/maps/android/data/parser/kml/PolyStyle$Companion; + public fun ()V + public fun (Ljava/lang/Integer;ZZ)V + public synthetic fun (Ljava/lang/Integer;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Z + public final fun component3 ()Z + public final fun copy (Ljava/lang/Integer;ZZ)Lcom/google/maps/android/data/parser/kml/PolyStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/PolyStyle;Ljava/lang/Integer;ZZILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/PolyStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getColor ()Ljava/lang/Integer; + public final fun getFill ()Z + public final fun getOutline ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/PolyStyle$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/PolyStyle$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/PolyStyle; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/PolyStyle;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/PolyStyle$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Polygon { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Polygon$Companion; + public fun (Lcom/google/maps/android/data/parser/kml/Boundary;Ljava/util/List;ZLcom/google/maps/android/data/parser/kml/AltitudeMode;)V + public synthetic fun (Lcom/google/maps/android/data/parser/kml/Boundary;Ljava/util/List;ZLcom/google/maps/android/data/parser/kml/AltitudeMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/parser/kml/Boundary; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Z + public final fun component4 ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun copy (Lcom/google/maps/android/data/parser/kml/Boundary;Ljava/util/List;ZLcom/google/maps/android/data/parser/kml/AltitudeMode;)Lcom/google/maps/android/data/parser/kml/Polygon; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Polygon;Lcom/google/maps/android/data/parser/kml/Boundary;Ljava/util/List;ZLcom/google/maps/android/data/parser/kml/AltitudeMode;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Polygon; + public fun equals (Ljava/lang/Object;)Z + public final fun getAltitudeMode ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun getExtrude ()Z + public final fun getInnerBoundaryIs ()Ljava/util/List; + public final fun getOuterBoundaryIs ()Lcom/google/maps/android/data/parser/kml/Boundary; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Polygon$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Polygon$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Polygon; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Polygon;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Polygon$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Style : com/google/maps/android/data/parser/kml/KmlFeature { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Style$Companion; + public fun ()V + public fun (Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/IconStyle;Lcom/google/maps/android/data/parser/kml/LabelStyle;Lcom/google/maps/android/data/parser/kml/LineStyle;Lcom/google/maps/android/data/parser/kml/PolyStyle;)V + public synthetic fun (Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/IconStyle;Lcom/google/maps/android/data/parser/kml/LabelStyle;Lcom/google/maps/android/data/parser/kml/LineStyle;Lcom/google/maps/android/data/parser/kml/PolyStyle;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/google/maps/android/data/parser/kml/IconStyle; + public final fun component3 ()Lcom/google/maps/android/data/parser/kml/LabelStyle; + public final fun component4 ()Lcom/google/maps/android/data/parser/kml/LineStyle; + public final fun component5 ()Lcom/google/maps/android/data/parser/kml/PolyStyle; + public final fun copy (Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/IconStyle;Lcom/google/maps/android/data/parser/kml/LabelStyle;Lcom/google/maps/android/data/parser/kml/LineStyle;Lcom/google/maps/android/data/parser/kml/PolyStyle;)Lcom/google/maps/android/data/parser/kml/Style; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Style;Ljava/lang/String;Lcom/google/maps/android/data/parser/kml/IconStyle;Lcom/google/maps/android/data/parser/kml/LabelStyle;Lcom/google/maps/android/data/parser/kml/LineStyle;Lcom/google/maps/android/data/parser/kml/PolyStyle;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Style; + public fun equals (Ljava/lang/Object;)Z + public final fun getIconStyle ()Lcom/google/maps/android/data/parser/kml/IconStyle; + public final fun getId ()Ljava/lang/String; + public final fun getLabelStyle ()Lcom/google/maps/android/data/parser/kml/LabelStyle; + public final fun getLineStyle ()Lcom/google/maps/android/data/parser/kml/LineStyle; + public final fun getPolyStyle ()Lcom/google/maps/android/data/parser/kml/PolyStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Style$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Style$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Style; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Style;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Style$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/StyleMap : com/google/maps/android/data/parser/kml/KmlFeature { + public static final field Companion Lcom/google/maps/android/data/parser/kml/StyleMap$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/google/maps/android/data/parser/kml/StyleMap; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/StyleMap;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/StyleMap; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/String; + public final fun getPairs ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/StyleMap$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/StyleMap$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/StyleMap; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/StyleMap;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/StyleMap$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Track { + public static final field Companion Lcom/google/maps/android/data/parser/kml/Track$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/util/List;Lcom/google/maps/android/data/parser/kml/AltitudeMode;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;Lcom/google/maps/android/data/parser/kml/AltitudeMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun copy (Ljava/util/List;Ljava/util/List;Lcom/google/maps/android/data/parser/kml/AltitudeMode;)Lcom/google/maps/android/data/parser/kml/Track; + public static synthetic fun copy$default (Lcom/google/maps/android/data/parser/kml/Track;Ljava/util/List;Ljava/util/List;Lcom/google/maps/android/data/parser/kml/AltitudeMode;ILjava/lang/Object;)Lcom/google/maps/android/data/parser/kml/Track; + public fun equals (Ljava/lang/Object;)Z + public final fun getAltitudeMode ()Lcom/google/maps/android/data/parser/kml/AltitudeMode; + public final fun getCoords ()Ljava/util/List; + public final fun getWhens ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/google/maps/android/data/parser/kml/Track$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/google/maps/android/data/parser/kml/Track$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/google/maps/android/data/parser/kml/Track; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/google/maps/android/data/parser/kml/Track;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/google/maps/android/data/parser/kml/Track$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class com/google/maps/android/data/renderer/DataRenderer { + public abstract fun addFeature (Lcom/google/maps/android/data/renderer/model/Feature;)V + public abstract fun addLayer (Lcom/google/maps/android/data/renderer/model/DataLayer;)V + public abstract fun clear ()V + public abstract fun removeFeature (Lcom/google/maps/android/data/renderer/model/Feature;)V + public abstract fun removeLayer (Lcom/google/maps/android/data/renderer/model/DataLayer;)V + public abstract fun render (Lcom/google/maps/android/data/renderer/model/DataScene;)V +} + +public abstract interface class com/google/maps/android/data/renderer/IconProvider { + public abstract fun loadIcon (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/google/maps/android/data/renderer/UrlIconProvider : com/google/maps/android/data/renderer/IconProvider { + public fun ()V + public fun (Lkotlinx/coroutines/CoroutineDispatcher;)V + public fun (Lkotlinx/coroutines/CoroutineDispatcher;Lcom/google/maps/android/data/kml/KmlUrlSanitizer;)V + public synthetic fun (Lkotlinx/coroutines/CoroutineDispatcher;Lcom/google/maps/android/data/kml/KmlUrlSanitizer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + protected fun loadBitmapFromUrl (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun loadIcon (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/data/renderer/mapper/GeoJsonMapper { + public static final field INSTANCE Lcom/google/maps/android/data/renderer/mapper/GeoJsonMapper; + public final fun toLayer (Lcom/google/maps/android/data/parser/geojson/GeoJsonObject;)Lcom/google/maps/android/data/renderer/model/DataLayer; + public final fun toScene (Lcom/google/maps/android/data/parser/geojson/GeoJsonObject;)Lcom/google/maps/android/data/renderer/model/DataScene; +} + +public final class com/google/maps/android/data/renderer/mapper/GeoJsonMapperKt { + public static final fun toLayer (Lcom/google/maps/android/data/parser/geojson/GeoJsonObject;)Lcom/google/maps/android/data/renderer/model/DataLayer; +} + +public final class com/google/maps/android/data/renderer/mapper/GpxMapper { + public static final field INSTANCE Lcom/google/maps/android/data/renderer/mapper/GpxMapper; + public final fun toScene (Lcom/google/maps/android/data/parser/gpx/Gpx;)Lcom/google/maps/android/data/renderer/model/DataScene; +} + +public final class com/google/maps/android/data/renderer/mapper/GpxMapperKt { + public static final fun toLayer (Lcom/google/maps/android/data/parser/gpx/Gpx;)Lcom/google/maps/android/data/renderer/model/DataLayer; +} + +public final class com/google/maps/android/data/renderer/mapper/KmlMapper { + public static final field INSTANCE Lcom/google/maps/android/data/renderer/mapper/KmlMapper; + public final fun toScene (Lcom/google/maps/android/data/parser/kml/Kml;)Lcom/google/maps/android/data/renderer/model/DataScene; +} + +public final class com/google/maps/android/data/renderer/mapper/KmlMapperKt { + public static final fun toLayer (Lcom/google/maps/android/data/parser/kml/Kml;)Lcom/google/maps/android/data/renderer/model/DataLayer; +} + +public final class com/google/maps/android/data/renderer/mapview/MapViewRenderer : com/google/maps/android/data/renderer/DataRenderer { + public static final field Companion Lcom/google/maps/android/data/renderer/mapview/MapViewRenderer$Companion; + public fun (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/data/renderer/IconProvider;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/data/renderer/IconProvider;Lcom/google/maps/android/collections/MarkerManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/data/renderer/IconProvider;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/data/renderer/IconProvider;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;)V + public fun (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/data/renderer/IconProvider;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)V + public synthetic fun (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/maps/android/data/renderer/IconProvider;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun addFeature (Lcom/google/maps/android/data/renderer/model/Feature;)V + public fun addLayer (Lcom/google/maps/android/data/renderer/model/DataLayer;)V + public final fun cacheImageData (Ljava/lang/String;Landroid/graphics/Bitmap;)V + public fun clear ()V + public final fun getFeatureForMapObject (Ljava/lang/Object;)Lcom/google/maps/android/data/renderer/model/Feature; + public final fun getUseAdvancedMarkers ()Z + public fun removeFeature (Lcom/google/maps/android/data/renderer/model/Feature;)V + public fun removeLayer (Lcom/google/maps/android/data/renderer/model/DataLayer;)V + public fun render (Lcom/google/maps/android/data/renderer/model/DataScene;)V + public final fun setUseAdvancedMarkers (Z)V +} + +public final class com/google/maps/android/data/renderer/mapview/MapViewRenderer$Companion { +} + +public final class com/google/maps/android/data/renderer/model/CompositeStyle : com/google/maps/android/data/renderer/model/Style { + public fun ()V + public fun (Lcom/google/maps/android/data/renderer/model/PointStyle;Lcom/google/maps/android/data/renderer/model/LineStyle;Lcom/google/maps/android/data/renderer/model/PolygonStyle;)V + public synthetic fun (Lcom/google/maps/android/data/renderer/model/PointStyle;Lcom/google/maps/android/data/renderer/model/LineStyle;Lcom/google/maps/android/data/renderer/model/PolygonStyle;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/renderer/model/PointStyle; + public final fun component2 ()Lcom/google/maps/android/data/renderer/model/LineStyle; + public final fun component3 ()Lcom/google/maps/android/data/renderer/model/PolygonStyle; + public final fun copy (Lcom/google/maps/android/data/renderer/model/PointStyle;Lcom/google/maps/android/data/renderer/model/LineStyle;Lcom/google/maps/android/data/renderer/model/PolygonStyle;)Lcom/google/maps/android/data/renderer/model/CompositeStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/CompositeStyle;Lcom/google/maps/android/data/renderer/model/PointStyle;Lcom/google/maps/android/data/renderer/model/LineStyle;Lcom/google/maps/android/data/renderer/model/PolygonStyle;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/CompositeStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getLineStyle ()Lcom/google/maps/android/data/renderer/model/LineStyle; + public final fun getPointStyle ()Lcom/google/maps/android/data/renderer/model/PointStyle; + public final fun getPolygonStyle ()Lcom/google/maps/android/data/renderer/model/PolygonStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/DataLayer { + public fun ()V + public fun (Ljava/util/List;Ljava/util/Map;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/google/maps/android/data/renderer/model/DataLayer; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/DataLayer;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/DataLayer; + public fun equals (Ljava/lang/Object;)Z + public final fun getBoundingBox ()Lcom/google/android/gms/maps/model/LatLngBounds; + public final fun getFeatures ()Ljava/util/List; + public final fun getProperties ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/DataScene { + public static final field Companion Lcom/google/maps/android/data/renderer/model/DataScene$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/renderer/model/DataScene; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/DataScene;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/DataScene; + public fun equals (Ljava/lang/Object;)Z + public final fun getBoundingBox ()Lcom/google/android/gms/maps/model/LatLngBounds; + public final fun getFeatures ()Ljava/util/List; + public final fun getLayers ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/DataScene$Companion { + public final fun fromFeatures (Ljava/util/List;)Lcom/google/maps/android/data/renderer/model/DataScene; +} + +public final class com/google/maps/android/data/renderer/model/Feature { + public fun (Lcom/google/maps/android/data/renderer/model/Geometry;Lcom/google/maps/android/data/renderer/model/Style;Ljava/util/Map;)V + public synthetic fun (Lcom/google/maps/android/data/renderer/model/Geometry;Lcom/google/maps/android/data/renderer/model/Style;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/maps/android/data/renderer/model/Geometry; + public final fun component2 ()Lcom/google/maps/android/data/renderer/model/Style; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Lcom/google/maps/android/data/renderer/model/Geometry;Lcom/google/maps/android/data/renderer/model/Style;Ljava/util/Map;)Lcom/google/maps/android/data/renderer/model/Feature; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/Feature;Lcom/google/maps/android/data/renderer/model/Geometry;Lcom/google/maps/android/data/renderer/model/Style;Ljava/util/Map;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/Feature; + public fun equals (Ljava/lang/Object;)Z + public final fun getGeometry ()Lcom/google/maps/android/data/renderer/model/Geometry; + public final fun getProperties ()Ljava/util/Map; + public final fun getStyle ()Lcom/google/maps/android/data/renderer/model/Style; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/renderer/model/Geometry { + public abstract fun getType ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/GroundOverlay : com/google/maps/android/data/renderer/model/Geometry { + public fun (DDDDF)V + public synthetic fun (DDDDFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()D + public final fun component4 ()D + public final fun component5 ()F + public final fun copy (DDDDF)Lcom/google/maps/android/data/renderer/model/GroundOverlay; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/GroundOverlay;DDDDFILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/GroundOverlay; + public fun equals (Ljava/lang/Object;)Z + public final fun getEast ()D + public final fun getLatLngBounds ()Lcom/google/android/gms/maps/model/LatLngBounds; + public final fun getNorth ()D + public final fun getRotation ()F + public final fun getSouth ()D + public fun getType ()Ljava/lang/String; + public final fun getWest ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/GroundOverlayStyle : com/google/maps/android/data/renderer/model/Style { + public fun (Ljava/lang/String;FFZ)V + public synthetic fun (Ljava/lang/String;FFZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()F + public final fun component3 ()F + public final fun component4 ()Z + public final fun copy (Ljava/lang/String;FFZ)Lcom/google/maps/android/data/renderer/model/GroundOverlayStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/GroundOverlayStyle;Ljava/lang/String;FFZILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/GroundOverlayStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getIconUrl ()Ljava/lang/String; + public final fun getTransparency ()F + public final fun getVisibility ()Z + public final fun getZIndex ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/LineString : com/google/maps/android/data/renderer/model/Geometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/renderer/model/LineString; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/LineString;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/LineString; + public fun equals (Ljava/lang/Object;)Z + public final fun getPoints ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/LineStyle : com/google/maps/android/data/renderer/model/Style { + public fun ()V + public fun (IFZFZZ)V + public synthetic fun (IFZFZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()F + public final fun component3 ()Z + public final fun component4 ()F + public final fun component5 ()Z + public final fun component6 ()Z + public final fun copy (IFZFZZ)Lcom/google/maps/android/data/renderer/model/LineStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/LineStyle;IFZFZZILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/LineStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getClickable ()Z + public final fun getColor ()I + public final fun getGeodesic ()Z + public final fun getVisible ()Z + public final fun getWidth ()F + public final fun getZIndex ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/MultiGeometry : com/google/maps/android/data/renderer/model/Geometry { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/google/maps/android/data/renderer/model/MultiGeometry; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/MultiGeometry;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/MultiGeometry; + public fun equals (Ljava/lang/Object;)Z + public final fun getGeometries ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/Point { + public fun (DDLjava/lang/Double;)V + public synthetic fun (DDLjava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun component3 ()Ljava/lang/Double; + public final fun copy (DDLjava/lang/Double;)Lcom/google/maps/android/data/renderer/model/Point; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/Point;DDLjava/lang/Double;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/Point; + public fun equals (Ljava/lang/Object;)Z + public final fun getAlt ()Ljava/lang/Double; + public final fun getLat ()D + public final fun getLng ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/PointGeometry : com/google/maps/android/data/renderer/model/Geometry { + public fun (Lcom/google/maps/android/data/renderer/model/Point;)V + public final fun component1 ()Lcom/google/maps/android/data/renderer/model/Point; + public final fun copy (Lcom/google/maps/android/data/renderer/model/Point;)Lcom/google/maps/android/data/renderer/model/PointGeometry; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/PointGeometry;Lcom/google/maps/android/data/renderer/model/Point;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/PointGeometry; + public fun equals (Ljava/lang/Object;)Z + public final fun getPoint ()Lcom/google/maps/android/data/renderer/model/Point; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/PointStyle : com/google/maps/android/data/renderer/model/Style { + public fun ()V + public fun (ILjava/lang/String;Ljava/lang/Float;FFFFLjava/lang/String;Ljava/lang/String;ZZZFF)V + public synthetic fun (ILjava/lang/String;Ljava/lang/Float;FFFFLjava/lang/String;Ljava/lang/String;ZZZFFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component10 ()Z + public final fun component11 ()Z + public final fun component12 ()Z + public final fun component13 ()F + public final fun component14 ()F + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Float; + public final fun component4 ()F + public final fun component5 ()F + public final fun component6 ()F + public final fun component7 ()F + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (ILjava/lang/String;Ljava/lang/Float;FFFFLjava/lang/String;Ljava/lang/String;ZZZFF)Lcom/google/maps/android/data/renderer/model/PointStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/PointStyle;ILjava/lang/String;Ljava/lang/Float;FFFFLjava/lang/String;Ljava/lang/String;ZZZFFILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/PointStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnchorU ()F + public final fun getAnchorV ()F + public final fun getColor ()I + public final fun getDraggable ()Z + public final fun getFlat ()Z + public final fun getHeading ()Ljava/lang/Float; + public final fun getIconUrl ()Ljava/lang/String; + public final fun getInfoWindowAnchorU ()F + public final fun getInfoWindowAnchorV ()F + public final fun getScale ()F + public final fun getSnippet ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getVisible ()Z + public final fun getZIndex ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/Polygon : com/google/maps/android/data/renderer/model/Geometry { + public fun (Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/google/maps/android/data/renderer/model/Polygon; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/Polygon;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/Polygon; + public fun equals (Ljava/lang/Object;)Z + public final fun getInnerBoundaries ()Ljava/util/List; + public final fun getOuterBoundary ()Ljava/util/List; + public fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/data/renderer/model/PolygonStyle : com/google/maps/android/data/renderer/model/Style { + public fun ()V + public fun (IIFZFZZ)V + public synthetic fun (IIFZFZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()F + public final fun component4 ()Z + public final fun component5 ()F + public final fun component6 ()Z + public final fun component7 ()Z + public final fun copy (IIFZFZZ)Lcom/google/maps/android/data/renderer/model/PolygonStyle; + public static synthetic fun copy$default (Lcom/google/maps/android/data/renderer/model/PolygonStyle;IIFZFZZILjava/lang/Object;)Lcom/google/maps/android/data/renderer/model/PolygonStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getClickable ()Z + public final fun getFillColor ()I + public final fun getGeodesic ()Z + public final fun getStrokeColor ()I + public final fun getStrokeWidth ()F + public final fun getVisible ()Z + public final fun getZIndex ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/google/maps/android/data/renderer/model/Style { +} + +public final class com/google/maps/android/ktx/utils/geojson/GeoJsonKt { + public static final fun geoJsonLayer (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; + public static final fun geoJsonLayer (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; + public static synthetic fun geoJsonLayer$default (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILjava/lang/Object;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; + public static synthetic fun geoJsonLayer$default (Lcom/google/android/gms/maps/GoogleMap;Lorg/json/JSONObject;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;ILjava/lang/Object;)Lcom/google/maps/android/data/geojson/GeoJsonLayer; +} + +public final class com/google/maps/android/ktx/utils/kml/KmlKt { + public static final fun kmlLayer (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;)Lcom/google/maps/android/data/kml/KmlLayer; + public static final fun kmlLayer (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;)Lcom/google/maps/android/data/kml/KmlLayer; + public static synthetic fun kmlLayer$default (Lcom/google/android/gms/maps/GoogleMap;ILandroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;ILjava/lang/Object;)Lcom/google/maps/android/data/kml/KmlLayer; + public static synthetic fun kmlLayer$default (Lcom/google/android/gms/maps/GoogleMap;Ljava/io/InputStream;Landroid/content/Context;Lcom/google/maps/android/collections/MarkerManager;Lcom/google/maps/android/collections/PolygonManager;Lcom/google/maps/android/collections/PolylineManager;Lcom/google/maps/android/collections/GroundOverlayManager;Lcom/google/maps/android/data/Renderer$ImagesCache;ILjava/lang/Object;)Lcom/google/maps/android/data/kml/KmlLayer; +} + +public final class com/google/maps/android/renderer/GoogleMapRenderer : com/google/maps/android/renderer/Renderer { + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public fun addLayer (Lcom/google/maps/android/renderer/model/Layer;)V + public fun clear ()V + public fun getLayers ()Ljava/util/Collection; + public fun removeLayer (Lcom/google/maps/android/renderer/model/Layer;)Z +} + +public abstract interface class com/google/maps/android/renderer/Renderer { + public abstract fun addLayer (Lcom/google/maps/android/renderer/model/Layer;)V + public abstract fun clear ()V + public abstract fun getLayers ()Ljava/util/Collection; + public abstract fun removeLayer (Lcom/google/maps/android/renderer/model/Layer;)Z +} + +public final class com/google/maps/android/renderer/model/Circle : com/google/maps/android/renderer/model/MapObject { + public fun (Lcom/google/android/gms/maps/model/LatLng;DFIIZZFLjava/util/List;)V + public synthetic fun (Lcom/google/android/gms/maps/model/LatLng;DFIIZZFLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/android/gms/maps/model/LatLng; + public final fun component2 ()D + public final fun component3 ()F + public final fun component4 ()I + public final fun component5 ()I + public final fun component6 ()Z + public final fun component7 ()Z + public final fun component8 ()F + public final fun component9 ()Ljava/util/List; + public final fun copy (Lcom/google/android/gms/maps/model/LatLng;DFIIZZFLjava/util/List;)Lcom/google/maps/android/renderer/model/Circle; + public static synthetic fun copy$default (Lcom/google/maps/android/renderer/model/Circle;Lcom/google/android/gms/maps/model/LatLng;DFIIZZFLjava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/renderer/model/Circle; + public fun equals (Ljava/lang/Object;)Z + public final fun getCenter ()Lcom/google/android/gms/maps/model/LatLng; + public final fun getFillColor ()I + public final fun getRadius ()D + public final fun getStrokeColor ()I + public final fun getStrokePattern ()Ljava/util/List; + public final fun getStrokeWidth ()F + public fun getType ()Lcom/google/maps/android/renderer/model/MapObject$Type; + public fun getZIndex ()F + public fun hashCode ()I + public final fun isClickable ()Z + public fun isVisible ()Z + public final fun setCenter (Lcom/google/android/gms/maps/model/LatLng;)V + public final fun setClickable (Z)V + public final fun setFillColor (I)V + public final fun setRadius (D)V + public final fun setStrokeColor (I)V + public final fun setStrokePattern (Ljava/util/List;)V + public final fun setStrokeWidth (F)V + public fun setVisible (Z)V + public fun setZIndex (F)V + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/renderer/model/Layer { + public fun ()V + public final fun addMapObject (Lcom/google/maps/android/renderer/model/MapObject;)V + public final fun addMapObjects (Ljava/util/Collection;)V + public final fun clear ()V + public final fun getMapObjects ()Ljava/util/Collection; + public final fun removeMapObject (Lcom/google/maps/android/renderer/model/MapObject;)Z +} + +public abstract interface class com/google/maps/android/renderer/model/MapObject { + public abstract fun getType ()Lcom/google/maps/android/renderer/model/MapObject$Type; + public abstract fun getZIndex ()F + public abstract fun isVisible ()Z + public abstract fun setVisible (Z)V + public abstract fun setZIndex (F)V +} + +public final class com/google/maps/android/renderer/model/MapObject$Type : java/lang/Enum { + public static final field CIRCLE Lcom/google/maps/android/renderer/model/MapObject$Type; + public static final field MARKER Lcom/google/maps/android/renderer/model/MapObject$Type; + public static final field POLYGON Lcom/google/maps/android/renderer/model/MapObject$Type; + public static final field POLYLINE Lcom/google/maps/android/renderer/model/MapObject$Type; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/google/maps/android/renderer/model/MapObject$Type; + public static fun values ()[Lcom/google/maps/android/renderer/model/MapObject$Type; +} + +public final class com/google/maps/android/renderer/model/Marker : com/google/maps/android/renderer/model/MapObject { + public fun (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Ljava/lang/String;Lcom/google/android/gms/maps/model/BitmapDescriptor;FFFFZZZF)V + public synthetic fun (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Ljava/lang/String;Lcom/google/android/gms/maps/model/BitmapDescriptor;FFFFZZZFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/google/android/gms/maps/model/LatLng; + public final fun component10 ()Z + public final fun component11 ()Z + public final fun component12 ()F + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/google/android/gms/maps/model/BitmapDescriptor; + public final fun component5 ()F + public final fun component6 ()F + public final fun component7 ()F + public final fun component8 ()F + public final fun component9 ()Z + public final fun copy (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Ljava/lang/String;Lcom/google/android/gms/maps/model/BitmapDescriptor;FFFFZZZF)Lcom/google/maps/android/renderer/model/Marker; + public static synthetic fun copy$default (Lcom/google/maps/android/renderer/model/Marker;Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Ljava/lang/String;Lcom/google/android/gms/maps/model/BitmapDescriptor;FFFFZZZFILjava/lang/Object;)Lcom/google/maps/android/renderer/model/Marker; + public fun equals (Ljava/lang/Object;)Z + public final fun getAlpha ()F + public final fun getAnchorU ()F + public final fun getAnchorV ()F + public final fun getDraggable ()Z + public final fun getFlat ()Z + public final fun getIcon ()Lcom/google/android/gms/maps/model/BitmapDescriptor; + public final fun getPosition ()Lcom/google/android/gms/maps/model/LatLng; + public final fun getRotation ()F + public final fun getSnippet ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun getType ()Lcom/google/maps/android/renderer/model/MapObject$Type; + public fun getZIndex ()F + public fun hashCode ()I + public fun isVisible ()Z + public final fun setAlpha (F)V + public final fun setAnchorU (F)V + public final fun setAnchorV (F)V + public final fun setDraggable (Z)V + public final fun setFlat (Z)V + public final fun setIcon (Lcom/google/android/gms/maps/model/BitmapDescriptor;)V + public final fun setPosition (Lcom/google/android/gms/maps/model/LatLng;)V + public final fun setRotation (F)V + public final fun setSnippet (Ljava/lang/String;)V + public final fun setTitle (Ljava/lang/String;)V + public fun setVisible (Z)V + public fun setZIndex (F)V + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/renderer/model/Polygon : com/google/maps/android/renderer/model/MapObject { + public fun (Ljava/util/List;Ljava/util/List;FIIZZZFILjava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;FIIZZZFILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun addHole (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun component10 ()I + public final fun component11 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()F + public final fun component4 ()I + public final fun component5 ()I + public final fun component6 ()Z + public final fun component7 ()Z + public final fun component8 ()Z + public final fun component9 ()F + public final fun copy (Ljava/util/List;Ljava/util/List;FIIZZZFILjava/util/List;)Lcom/google/maps/android/renderer/model/Polygon; + public static synthetic fun copy$default (Lcom/google/maps/android/renderer/model/Polygon;Ljava/util/List;Ljava/util/List;FIIZZZFILjava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/renderer/model/Polygon; + public fun equals (Ljava/lang/Object;)Z + public final fun getFillColor ()I + public final fun getHoles ()Ljava/util/List; + public final fun getPoints ()Ljava/util/List; + public final fun getStrokeColor ()I + public final fun getStrokeJointType ()I + public final fun getStrokePattern ()Ljava/util/List; + public final fun getStrokeWidth ()F + public fun getType ()Lcom/google/maps/android/renderer/model/MapObject$Type; + public fun getZIndex ()F + public fun hashCode ()I + public final fun isClickable ()Z + public final fun isGeodesic ()Z + public fun isVisible ()Z + public final fun setClickable (Z)V + public final fun setFillColor (I)V + public final fun setGeodesic (Z)V + public final fun setHoles (Ljava/util/List;)V + public final fun setPoints (Ljava/util/List;)V + public final fun setStrokeColor (I)V + public final fun setStrokeJointType (I)V + public final fun setStrokePattern (Ljava/util/List;)V + public final fun setStrokeWidth (F)V + public fun setVisible (Z)V + public fun setZIndex (F)V + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/renderer/model/Polyline : com/google/maps/android/renderer/model/MapObject { + public fun (Ljava/util/List;FIZZZFLcom/google/android/gms/maps/model/Cap;Lcom/google/android/gms/maps/model/Cap;ILjava/util/List;)V + public synthetic fun (Ljava/util/List;FIZZZFLcom/google/android/gms/maps/model/Cap;Lcom/google/android/gms/maps/model/Cap;ILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component10 ()I + public final fun component11 ()Ljava/util/List; + public final fun component2 ()F + public final fun component3 ()I + public final fun component4 ()Z + public final fun component5 ()Z + public final fun component6 ()Z + public final fun component7 ()F + public final fun component8 ()Lcom/google/android/gms/maps/model/Cap; + public final fun component9 ()Lcom/google/android/gms/maps/model/Cap; + public final fun copy (Ljava/util/List;FIZZZFLcom/google/android/gms/maps/model/Cap;Lcom/google/android/gms/maps/model/Cap;ILjava/util/List;)Lcom/google/maps/android/renderer/model/Polyline; + public static synthetic fun copy$default (Lcom/google/maps/android/renderer/model/Polyline;Ljava/util/List;FIZZZFLcom/google/android/gms/maps/model/Cap;Lcom/google/android/gms/maps/model/Cap;ILjava/util/List;ILjava/lang/Object;)Lcom/google/maps/android/renderer/model/Polyline; + public fun equals (Ljava/lang/Object;)Z + public final fun getColor ()I + public final fun getEndCap ()Lcom/google/android/gms/maps/model/Cap; + public final fun getJointType ()I + public final fun getPattern ()Ljava/util/List; + public final fun getPoints ()Ljava/util/List; + public final fun getStartCap ()Lcom/google/android/gms/maps/model/Cap; + public fun getType ()Lcom/google/maps/android/renderer/model/MapObject$Type; + public final fun getWidth ()F + public fun getZIndex ()F + public fun hashCode ()I + public final fun isClickable ()Z + public final fun isGeodesic ()Z + public fun isVisible ()Z + public final fun setClickable (Z)V + public final fun setColor (I)V + public final fun setEndCap (Lcom/google/android/gms/maps/model/Cap;)V + public final fun setGeodesic (Z)V + public final fun setJointType (I)V + public final fun setPattern (Ljava/util/List;)V + public final fun setPoints (Ljava/util/List;)V + public final fun setStartCap (Lcom/google/android/gms/maps/model/Cap;)V + public fun setVisible (Z)V + public final fun setWidth (F)V + public fun setZIndex (F)V + public fun toString ()Ljava/lang/String; +} + diff --git a/data/build.gradle.kts b/data/build.gradle.kts index d81754ca4..c977da6c0 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -47,6 +47,7 @@ android { resourcePrefix = "amu_" kotlin { + explicitApi() compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } diff --git a/data/src/main/java/com/google/maps/android/data/DataLayerLoader.kt b/data/src/main/java/com/google/maps/android/data/DataLayerLoader.kt index d1ff29131..5dc923b14 100644 --- a/data/src/main/java/com/google/maps/android/data/DataLayerLoader.kt +++ b/data/src/main/java/com/google/maps/android/data/DataLayerLoader.kt @@ -37,7 +37,7 @@ import java.nio.charset.StandardCharsets * Handles loading from assets or streams, determining the correct parser based on * file extension or content sniffing. */ -class DataLayerLoader( +public class DataLayerLoader( private val context: Context, private val dispatcher: CoroutineDispatcher = Dispatchers.IO, ) { @@ -48,7 +48,7 @@ class DataLayerLoader( * @param assetName The name of the asset file. * @return The loaded DataLayer, or null if loading failed. */ - suspend fun loadAsset(assetName: String): DataLayer? = + public suspend fun loadAsset(assetName: String): DataLayer? = withContext(dispatcher) { try { val inputStream = context.assets.open(assetName) @@ -66,7 +66,7 @@ class DataLayerLoader( * @param fileName The name of the file (optional), used for extension matching. * @return The loaded DataLayer, or null if loading failed. */ - suspend fun loadInputStream( + public suspend fun loadInputStream( inputStream: InputStream, fileName: String?, ): DataLayer? { diff --git a/data/src/main/java/com/google/maps/android/data/Layer.kt b/data/src/main/java/com/google/maps/android/data/Layer.kt index 2c1cb85aa..e6705355f 100644 --- a/data/src/main/java/com/google/maps/android/data/Layer.kt +++ b/data/src/main/java/com/google/maps/android/data/Layer.kt @@ -29,9 +29,9 @@ public abstract class Layer { protected var mGoogleMap: GoogleMap? = null // Default styles - protected var mDefaultPointStyle = GeoJsonPointStyle() - protected var mDefaultLineStringStyle = GeoJsonLineStringStyle() - protected var mDefaultPolygonStyle = GeoJsonPolygonStyle() + protected var mDefaultPointStyle: GeoJsonPointStyle = GeoJsonPointStyle() + protected var mDefaultLineStringStyle: GeoJsonLineStringStyle = GeoJsonLineStringStyle() + protected var mDefaultPolygonStyle: GeoJsonPolygonStyle = GeoJsonPolygonStyle() public abstract fun getMap(): GoogleMap? diff --git a/data/src/main/java/com/google/maps/android/data/Style.kt b/data/src/main/java/com/google/maps/android/data/Style.kt index f827bc5d2..9e073484d 100644 --- a/data/src/main/java/com/google/maps/android/data/Style.kt +++ b/data/src/main/java/com/google/maps/android/data/Style.kt @@ -76,7 +76,7 @@ public abstract class Style : Observable() { mPolygonOptions.fillColor(fillColor) } - companion object { + public companion object { private const val LOG_TAG = "Style" } } diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLineStringStyle.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLineStringStyle.kt index a7865c7fb..37cb056e7 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLineStringStyle.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLineStringStyle.kt @@ -28,7 +28,7 @@ import com.google.maps.android.data.Style public class GeoJsonLineStringStyle : Style(), GeoJsonStyle { - override fun getGeometryType(): Array = GEOMETRY_TYPE + public override fun getGeometryType(): Array = GEOMETRY_TYPE public var color: Int get() = mPolylineOptions.color @@ -127,7 +127,7 @@ public class GeoJsonLineStringStyle : public fun getEndCap(): Cap = mPolylineOptions.endCap - companion object { + public companion object { private val GEOMETRY_TYPE = arrayOf("LineString", "MultiLineString", "GeometryCollection") } } diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPointStyle.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPointStyle.kt index baf238370..27e7b477e 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPointStyle.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPointStyle.kt @@ -27,7 +27,7 @@ import com.google.maps.android.data.Style public class GeoJsonPointStyle : Style(), GeoJsonStyle { - override fun getGeometryType(): Array = GEOMETRY_TYPE + public override fun getGeometryType(): Array = GEOMETRY_TYPE public fun getAlpha(): Float = mMarkerOptions.alpha @@ -155,7 +155,7 @@ public class GeoJsonPointStyle : append("\n}\n") }.toString() - companion object { + public companion object { private val GEOMETRY_TYPE = arrayOf("Point", "MultiPoint", "GeometryCollection") } } diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygon.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygon.kt index af75c1908..5b3d54287 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygon.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygon.kt @@ -34,13 +34,13 @@ public class GeoJsonPolygon( public fun getCoordinates(): List> = coordinates - override fun getGeometryObject(): List> = getCoordinates() + public override fun getGeometryObject(): List> = getCoordinates() - override fun getGeometryType(): String = getType() + public override fun getGeometryType(): String = getType() - override fun getOuterBoundaryCoordinates(): List = coordinates[POLYGON_OUTER_COORDINATE_INDEX] + public override fun getOuterBoundaryCoordinates(): List = coordinates[POLYGON_OUTER_COORDINATE_INDEX] - override fun getInnerBoundaryCoordinates(): List> { + public override fun getInnerBoundaryCoordinates(): List> { val innerBoundary = ArrayList>() for (i in POLYGON_INNER_COORDINATE_INDEX until coordinates.size) { innerBoundary.add(coordinates[i]) @@ -48,14 +48,14 @@ public class GeoJsonPolygon( return innerBoundary } - override fun toString(): String { + public override fun toString(): String { val sb = StringBuilder(GEOMETRY_TYPE).append("{") sb.append("\n coordinates=").append(coordinates) sb.append("\n}\n") return sb.toString() } - companion object { + public companion object { private const val GEOMETRY_TYPE = "Polygon" private const val POLYGON_OUTER_COORDINATE_INDEX = 0 private const val POLYGON_INNER_COORDINATE_INDEX = 1 diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygonStyle.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygonStyle.kt index af04087d0..82df23ac6 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygonStyle.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonPolygonStyle.kt @@ -27,7 +27,7 @@ import com.google.maps.android.data.Style public class GeoJsonPolygonStyle : Style(), GeoJsonStyle { - override fun getGeometryType(): Array = GEOMETRY_TYPE + public override fun getGeometryType(): Array = GEOMETRY_TYPE public var fillColor: Int get() = mPolygonOptions.fillColor @@ -126,7 +126,7 @@ public class GeoJsonPolygonStyle : public fun isClickable(): Boolean = mPolygonOptions.isClickable - companion object { + public companion object { private val GEOMETRY_TYPE = arrayOf("Polygon", "MultiPolygon", "GeometryCollection") } } diff --git a/data/src/main/java/com/google/maps/android/data/kml/KmlPolygon.kt b/data/src/main/java/com/google/maps/android/data/kml/KmlPolygon.kt index d90df7d63..57e4f9fbe 100644 --- a/data/src/main/java/com/google/maps/android/data/kml/KmlPolygon.kt +++ b/data/src/main/java/com/google/maps/android/data/kml/KmlPolygon.kt @@ -31,9 +31,9 @@ public class KmlPolygon( requireNotNull(outerBoundaryCoordinates) { "Outer boundary coordinates cannot be null" } } - override fun getGeometryType(): String = GEOMETRY_TYPE + public override fun getGeometryType(): String = GEOMETRY_TYPE - override fun getGeometryObject(): List> { + public override fun getGeometryObject(): List> { val coordinates = ArrayList>() coordinates.add(outerBoundaryCoordinates) if (innerBoundaryCoordinates != null) { @@ -42,11 +42,11 @@ public class KmlPolygon( return coordinates } - override fun getOuterBoundaryCoordinates(): List = outerBoundaryCoordinates + public override fun getOuterBoundaryCoordinates(): List = outerBoundaryCoordinates - override fun getInnerBoundaryCoordinates(): List> = innerBoundaryCoordinates ?: emptyList() + public override fun getInnerBoundaryCoordinates(): List> = innerBoundaryCoordinates ?: emptyList() - override fun toString(): String = + public override fun toString(): String = StringBuilder(GEOMETRY_TYPE) .apply { append("{") @@ -55,7 +55,7 @@ public class KmlPolygon( append("\n}\n") }.toString() - companion object { - public const val GEOMETRY_TYPE = "Polygon" + public companion object { + public const val GEOMETRY_TYPE: String = "Polygon" } } diff --git a/data/src/main/java/com/google/maps/android/data/kml/KmlStyle.kt b/data/src/main/java/com/google/maps/android/data/kml/KmlStyle.kt index 6f676d21a..696b95b09 100644 --- a/data/src/main/java/com/google/maps/android/data/kml/KmlStyle.kt +++ b/data/src/main/java/com/google/maps/android/data/kml/KmlStyle.kt @@ -177,7 +177,7 @@ public class KmlStyle : Style() { clickable(mPolygonOptions.isClickable) } - override fun toString(): String = + public override fun toString(): String = StringBuilder("Style") .apply { append("{") @@ -190,7 +190,7 @@ public class KmlStyle : Style() { append("\n}\n") }.toString() - companion object { + public companion object { private const val HSV_VALUES = 3 private const val HUE_VALUE = 0 diff --git a/data/src/main/java/com/google/maps/android/data/kml/KmlUrlSanitizer.kt b/data/src/main/java/com/google/maps/android/data/kml/KmlUrlSanitizer.kt index b76636d06..69c89be4d 100644 --- a/data/src/main/java/com/google/maps/android/data/kml/KmlUrlSanitizer.kt +++ b/data/src/main/java/com/google/maps/android/data/kml/KmlUrlSanitizer.kt @@ -19,12 +19,12 @@ package com.google.maps.android.data.kml * Interface for sanitizing URLs in KML documents. * Developers can implement this to control which external resources (images, etc.) are loaded. */ -fun interface KmlUrlSanitizer { +public fun interface KmlUrlSanitizer { /** * Sanitizes a URL before it is used to fetch a resource. * * @param url The raw URL from the KML. * @return A safe, validated URL string, or null to block this resource. */ - fun sanitizeUrl(url: String): String? + public fun sanitizeUrl(url: String): String? } diff --git a/data/src/main/java/com/google/maps/android/data/parser/GeoFileParser.kt b/data/src/main/java/com/google/maps/android/data/parser/GeoFileParser.kt index f70a3353d..8b1cdeb72 100644 --- a/data/src/main/java/com/google/maps/android/data/parser/GeoFileParser.kt +++ b/data/src/main/java/com/google/maps/android/data/parser/GeoFileParser.kt @@ -21,4 +21,4 @@ import java.io.InputStream * Interface for a file parser that transforms an input stream * from a specific format into the unified [GeoData] model. */ -interface GeoFileParser +public interface GeoFileParser diff --git a/data/src/main/java/com/google/maps/android/data/parser/Model.kt b/data/src/main/java/com/google/maps/android/data/parser/Model.kt index 640f01061..3c6b8d2bf 100644 --- a/data/src/main/java/com/google/maps/android/data/parser/Model.kt +++ b/data/src/main/java/com/google/maps/android/data/parser/Model.kt @@ -21,41 +21,41 @@ import com.google.maps.android.data.parser.kml.Style * A generic container for geographic data parsed from any file. * It holds a collection of features, each representing a distinct entity on the map. */ -data class GeoData( - val features: List, +public data class GeoData( + public val features: List, ) /** * Represents a single, distinct geographic entity, such as a placemark, a route, or a defined area. * It combines geometry (the 'what' and 'where') with properties (the 'metadata') and styling. */ -data class Feature( - val geometry: Geometry, - val properties: Map = emptyMap(), // For metadata like name, description, etc. - val style: Style? = null, +public data class Feature( + public val geometry: Geometry, + public val properties: Map = emptyMap(), // For metadata like name, description, etc. + public val style: Style? = null, ) /** * A sealed interface representing the geometric shape of a feature. */ -sealed interface Geometry { - data class Point( - val lat: Double, - val lon: Double, - val alt: Double?, +public sealed interface Geometry { + public data class Point( + public val lat: Double, + public val lon: Double, + public val alt: Double?, ) : Geometry - data class LineString( - val points: List, + public data class LineString( + public val points: List, ) : Geometry - data class Polygon( - val shell: List, - val holes: List> = emptyList(), + public data class Polygon( + public val shell: List, + public val holes: List> = emptyList(), ) : Geometry - data class GeometryCollection( - val geometries: List, + public data class GeometryCollection( + public val geometries: List, ) : Geometry } @@ -63,8 +63,8 @@ sealed interface Geometry { * Represents styling information that can be applied to a feature. * Properties are nullable as not all formats or features will specify them. */ -data class Style( - val strokeColor: String?, // e.g., "#RRGGBB" or "#AARRGGBB" - val strokeWidth: Float?, - val fillColor: String?, +public data class Style( + public val strokeColor: String?, // e.g., "#RRGGBB" or "#AARRGGBB" + public val strokeWidth: Float?, + public val fillColor: String?, ) diff --git a/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonObjects.kt b/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonObjects.kt index f9abf798a..7004ed3bf 100644 --- a/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonObjects.kt +++ b/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonObjects.kt @@ -26,7 +26,7 @@ package com.google.maps.android.data.parser.geojson * @property lng The longitude of the coordinate. * @property alt The altitude of the coordinate, in meters. Optional. */ -data class Coordinates(val lat: Double, val lng: Double, val alt: Double? = null) { +public data class Coordinates(public val lat: Double, public val lng: Double, public val alt: Double? = null) { init { require(lat.isFinite() && lng.isFinite() && (alt == null || alt.isFinite())) { "GeoJSON coordinate contains a non-finite value" @@ -35,65 +35,65 @@ data class Coordinates(val lat: Double, val lng: Double, val alt: Double? = null } // Using a sealed interface for all GeoJSON objects -sealed interface GeoJsonObject { - val type: String +public sealed interface GeoJsonObject { + public val type: String } // Sealed interface for Geometry objects -sealed interface GeoJsonGeometry : GeoJsonObject +public sealed interface GeoJsonGeometry : GeoJsonObject -data class GeoJsonPoint( - val coordinates: Coordinates +public data class GeoJsonPoint( + public val coordinates: Coordinates ) : GeoJsonGeometry { - override val type: String = "Point" + public override val type: String = "Point" } -data class GeoJsonMultiPoint( - val coordinates: List +public data class GeoJsonMultiPoint( + public val coordinates: List ) : GeoJsonGeometry { - override val type: String = "MultiPoint" + public override val type: String = "MultiPoint" } -data class GeoJsonLineString( - val coordinates: List +public data class GeoJsonLineString( + public val coordinates: List ) : GeoJsonGeometry { - override val type: String = "LineString" + public override val type: String = "LineString" } -data class GeoJsonMultiLineString( - val coordinates: List> +public data class GeoJsonMultiLineString( + public val coordinates: List> ) : GeoJsonGeometry { - override val type: String = "MultiLineString" + public override val type: String = "MultiLineString" } -data class GeoJsonPolygon( - val coordinates: List> +public data class GeoJsonPolygon( + public val coordinates: List> ) : GeoJsonGeometry { - override val type: String = "Polygon" + public override val type: String = "Polygon" } -data class GeoJsonMultiPolygon( - val coordinates: List>> +public data class GeoJsonMultiPolygon( + public val coordinates: List>> ) : GeoJsonGeometry { - override val type: String = "MultiPolygon" + public override val type: String = "MultiPolygon" } -data class GeoJsonGeometryCollection( - val geometries: List +public data class GeoJsonGeometryCollection( + public val geometries: List ) : GeoJsonGeometry { - override val type: String = "GeometryCollection" + public override val type: String = "GeometryCollection" } -data class GeoJsonFeature( - val geometry: GeoJsonGeometry?, - val properties: Map?, - val id: String? = null // id is optional and can be string or number +public data class GeoJsonFeature( + public val geometry: GeoJsonGeometry?, + public val properties: Map?, + public val id: String? = null // id is optional and can be string or number ) : GeoJsonObject { - override val type: String = "Feature" + public override val type: String = "Feature" } -data class GeoJsonFeatureCollection( - val features: List +public data class GeoJsonFeatureCollection( + public val features: List ) : GeoJsonObject { - override val type: String = "FeatureCollection" + public override val type: String = "FeatureCollection" } \ No newline at end of file diff --git a/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonParser.kt b/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonParser.kt index df490bc09..09003d5d1 100644 --- a/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonParser.kt +++ b/data/src/main/java/com/google/maps/android/data/parser/geojson/GeoJsonParser.kt @@ -25,11 +25,11 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import java.io.InputStream -class GeoJsonParser( +public class GeoJsonParser @JvmOverloads public constructor( private val maxInputSize: Long = DEFAULT_MAX_INPUT_SIZE, private val maxStructuralDepth: Int = DEFAULT_MAX_STRUCTURAL_DEPTH, ) { - fun parse(inputStream: InputStream): GeoJsonObject? { + public fun parse(inputStream: InputStream): GeoJsonObject? { // Bound the untrusted input *before* materialising it. [Json.parseToJsonElement] reads the // whole document into an in-memory tree, so without these guards a hostile document can // exhaust memory (a wide document) or overflow the parser's stack (a deeply nested one) @@ -59,8 +59,8 @@ class GeoJsonParser( } } - companion object { - const val MAX_GEOMETRY_DEPTH = 20 + public companion object { + public const val MAX_GEOMETRY_DEPTH: Int = 20 /** * Maximum number of characters read from an untrusted document. [Json.parseToJsonElement] @@ -69,7 +69,7 @@ class GeoJsonParser( * above any realistic layer (~60x the largest bundled sample); raise it via the constructor * for trusted large sources. */ - const val DEFAULT_MAX_INPUT_SIZE = 10L * 1024 * 1024 + public const val DEFAULT_MAX_INPUT_SIZE: Long = 10L * 1024 * 1024 /** * Maximum raw structural nesting (`{`/`[`) accepted. [Json.parseToJsonElement] parses nested @@ -78,11 +78,11 @@ class GeoJsonParser( * safe on small Android thread stacks while remaining far above any legitimate GeoJSON, whose * structural depth is well under 100 even with maximally nested geometries. */ - const val DEFAULT_MAX_STRUCTURAL_DEPTH = 512 + public const val DEFAULT_MAX_STRUCTURAL_DEPTH: Int = 512 - val SUPPORTED_EXTENSIONS = setOf("json", "geojson") + public val SUPPORTED_EXTENSIONS: Set = setOf("json", "geojson") - fun canParse(header: String): Boolean = header.trimStart().startsWith("{") + public fun canParse(header: String): Boolean = header.trimStart().startsWith("{") } } diff --git a/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxModel.kt b/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxModel.kt index d501f50b0..e302ae370 100644 --- a/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxModel.kt +++ b/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxModel.kt @@ -23,37 +23,37 @@ private const val GPX_NAMESPACE = "http://www.topografix.com/GPX/1/1" @Serializable @XmlSerialName("gpx", namespace = GPX_NAMESPACE, prefix = "") -data class Gpx( +public data class Gpx( @XmlSerialName("version") - val version: String = "1.1", + public val version: String = "1.1", @XmlSerialName("creator") - val creator: String? = null, + public val creator: String? = null, @XmlElement(true) @XmlSerialName("metadata", namespace = GPX_NAMESPACE, prefix = "") - val metadata: Metadata? = null, + public val metadata: Metadata? = null, @XmlElement(true) @XmlSerialName("wpt", namespace = GPX_NAMESPACE, prefix = "") - val waypoints: List = emptyList(), + public val waypoints: List = emptyList(), @XmlElement(true) @XmlSerialName("rte", namespace = GPX_NAMESPACE, prefix = "") - val routes: List = emptyList(), + public val routes: List = emptyList(), @XmlElement(true) @XmlSerialName("trk", namespace = GPX_NAMESPACE, prefix = "") - val tracks: List = emptyList(), + public val tracks: List = emptyList(), ) @Serializable @XmlSerialName("metadata", namespace = GPX_NAMESPACE, prefix = "") -data class Metadata( +public data class Metadata( @XmlElement(true) @XmlSerialName("name", namespace = GPX_NAMESPACE, prefix = "") - val name: String? = null, + public val name: String? = null, @XmlElement(true) @XmlSerialName("desc", namespace = GPX_NAMESPACE, prefix = "") - val desc: String? = null, + public val desc: String? = null, @XmlElement(true) @XmlSerialName("time", namespace = GPX_NAMESPACE, prefix = "") - val time: String? = null, + public val time: String? = null, ) /** @@ -69,31 +69,31 @@ data class Metadata( */ @Serializable @XmlSerialName("wpt", namespace = GPX_NAMESPACE, prefix = "") -data class Wpt( +public data class Wpt( @XmlSerialName("lat") - val lat: Double, + public val lat: Double, @XmlSerialName("lon") - val lon: Double, + public val lon: Double, @XmlElement(true) @XmlSerialName("ele", namespace = GPX_NAMESPACE, prefix = "") - val ele: Double? = null, + public val ele: Double? = null, @XmlElement(true) @XmlSerialName("time", namespace = GPX_NAMESPACE, prefix = "") - val time: String? = null, + public val time: String? = null, @XmlElement(true) @XmlSerialName("name", namespace = GPX_NAMESPACE, prefix = "") - val name: String? = null, + public val name: String? = null, @XmlElement(true) @XmlSerialName("desc", namespace = GPX_NAMESPACE, prefix = "") - val desc: String? = null, + public val desc: String? = null, @XmlElement(true) @XmlSerialName("sym", namespace = GPX_NAMESPACE, prefix = "") - val sym: String? = null, + public val sym: String? = null, ) { /** * Descriptive alias for [ele] (elevation in meters). */ - val elevation: Double? + public val elevation: Double? get() = ele init { @@ -105,36 +105,36 @@ data class Wpt( @Serializable @XmlSerialName("rte", namespace = GPX_NAMESPACE, prefix = "") -data class Rte( +public data class Rte( @XmlElement(true) @XmlSerialName("name", namespace = GPX_NAMESPACE, prefix = "") - val name: String? = null, + public val name: String? = null, @XmlElement(true) @XmlSerialName("desc", namespace = GPX_NAMESPACE, prefix = "") - val desc: String? = null, + public val desc: String? = null, @XmlElement(true) @XmlSerialName("rtept", namespace = GPX_NAMESPACE, prefix = "") - val routePoints: List = emptyList(), + public val routePoints: List = emptyList(), ) @Serializable @XmlSerialName("trk", namespace = GPX_NAMESPACE, prefix = "") -data class Trk( +public data class Trk( @XmlElement(true) @XmlSerialName("name", namespace = GPX_NAMESPACE, prefix = "") - val name: String? = null, + public val name: String? = null, @XmlElement(true) @XmlSerialName("desc", namespace = GPX_NAMESPACE, prefix = "") - val desc: String? = null, + public val desc: String? = null, @XmlElement(true) @XmlSerialName("trkseg", namespace = GPX_NAMESPACE, prefix = "") - val trackSegments: List = emptyList(), + public val trackSegments: List = emptyList(), ) @Serializable @XmlSerialName("trkseg", namespace = GPX_NAMESPACE, prefix = "") -data class TrkSeg( +public data class TrkSeg( @XmlElement(true) @XmlSerialName("trkpt", namespace = GPX_NAMESPACE, prefix = "") - val trackPoints: List = emptyList(), + public val trackPoints: List = emptyList(), ) diff --git a/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxParser.kt b/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxParser.kt index 313447175..e3ff1bf87 100644 --- a/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxParser.kt +++ b/data/src/main/java/com/google/maps/android/data/parser/gpx/GpxParser.kt @@ -26,7 +26,7 @@ import java.nio.charset.StandardCharsets * Uses the `pdvrieze/xmlutil` library for XML parsing. * Supports parsing GPX 1.0 and 1.1 formats (normalizing 1.0 to 1.1). */ -class GpxParser { +public class GpxParser public constructor() { private val xml = XML { defaultPolicy { @@ -35,7 +35,7 @@ class GpxParser { isCollectingNSAttributes = true } - fun parse(inputStream: InputStream): Gpx { + public fun parse(inputStream: InputStream): Gpx { val xmlContent = inputStream.bufferedReader(StandardCharsets.UTF_8).use { it.readText() } // Normalize GPX 1.0 namespace to 1.1 to allow parsing with the same model // (The structure for waypoints and tracks is compatible enough for our needs) @@ -43,9 +43,9 @@ class GpxParser { return xml.decodeFromString(normalizedXml) } - companion object { - val SUPPORTED_EXTENSIONS = setOf("gpx") + public companion object { + public val SUPPORTED_EXTENSIONS: Set = setOf("gpx") - fun canParse(header: String): Boolean = header.contains(" = emptyMap(), + public val images: Map = emptyMap(), ) @Serializable @XmlSerialName("GroundOverlay", namespace = KML_NAMESPACE, prefix = "") -data class GroundOverlay( +public data class GroundOverlay( @XmlElement(true) @XmlSerialName("name", namespace = KML_NAMESPACE, prefix = "") - val name: String? = null, + public val name: String? = null, @XmlElement(true) @XmlSerialName("drawOrder", namespace = KML_NAMESPACE, prefix = "") - val drawOrder: Int? = null, + public val drawOrder: Int? = null, @XmlElement(true) @XmlSerialName("visibility", namespace = KML_NAMESPACE, prefix = "") @Serializable(with = BooleanSerializer::class) - val visibility: Boolean = true, + public val visibility: Boolean = true, @XmlElement(true) @XmlSerialName("color", namespace = KML_NAMESPACE, prefix = "") @Serializable(with = ColorSerializer::class) - val color: Int? = null, + public val color: Int? = null, @XmlElement(true) @XmlSerialName("Icon", namespace = KML_NAMESPACE, prefix = "") - val icon: Icon? = null, + public val icon: Icon? = null, @XmlElement(true) @XmlSerialName("LatLonBox", namespace = KML_NAMESPACE, prefix = "") - val latLonBox: LatLonBox? = null, + public val latLonBox: LatLonBox? = null, @XmlElement(true) @XmlSerialName("LatLonQuad", namespace = KML_NAMESPACE, prefix = "") - val latLonQuad: LatLonQuad? = null, + public val latLonQuad: LatLonQuad? = null, ) @Serializable @XmlSerialName("Icon", namespace = KML_NAMESPACE, prefix = "") -data class Icon( +public data class Icon( @XmlElement(true) @XmlSerialName("href", namespace = KML_NAMESPACE, prefix = "") - val href: String? = null, + public val href: String? = null, ) @Serializable @XmlSerialName("LatLonBox", namespace = KML_NAMESPACE, prefix = "") -data class LatLonBox( +public data class LatLonBox( @XmlElement(true) @XmlSerialName("north", namespace = KML_NAMESPACE, prefix = "") - val north: Double, + public val north: Double, @XmlElement(true) @XmlSerialName("south", namespace = KML_NAMESPACE, prefix = "") - val south: Double, + public val south: Double, @XmlElement(true) @XmlSerialName("east", namespace = KML_NAMESPACE, prefix = "") - val east: Double, + public val east: Double, @XmlElement(true) @XmlSerialName("west", namespace = KML_NAMESPACE, prefix = "") - val west: Double, + public val west: Double, @XmlElement(true) @XmlSerialName("rotation", namespace = KML_NAMESPACE, prefix = "") - val rotation: Double? = null, + public val rotation: Double? = null, ) @Serializable @XmlSerialName("LatLonQuad", namespace = KML_NAMESPACE, prefix = "") -data class LatLonQuad( +public data class LatLonQuad( @XmlElement(true) @XmlSerialName("coordinates", namespace = KML_NAMESPACE, prefix = "") @Serializable(with = LatLngAltListSerializer::class) - val coordinates: List = emptyList(), + public val coordinates: List = emptyList(), ) -fun Kml.findByPlacemarksById(id: String): List = +public fun Kml.findByPlacemarksById(id: String): List = buildList { document?.placemarks?.filter { it.id == id }?.let { addAll(it) } } @Serializable @XmlSerialName("Document", namespace = KML_NAMESPACE, prefix = "") -data class Document( +public data class Document( @XmlElement(true) @XmlSerialName("name", namespace = KML_NAMESPACE, prefix = "") - val name: String? = null, + public val name: String? = null, @XmlElement(true) @XmlSerialName("description", namespace = KML_NAMESPACE, prefix = "") - val description: String? = null, + public val description: String? = null, @XmlElement(true) @XmlSerialName("Folder", namespace = KML_NAMESPACE, prefix = "") - val folders: List = emptyList(), + public val folders: List = emptyList(), @XmlElement(true) @XmlSerialName("Placemark", namespace = KML_NAMESPACE, prefix = "") - val placemarks: List = emptyList(), + public val placemarks: List = emptyList(), @XmlElement(true) @XmlSerialName("Style", namespace = KML_NAMESPACE, prefix = "") - val styles: List