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/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/README.md b/README.md index 5b22a01ea..7334026f4 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+`)**: 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,65 @@ Full guides for using the utilities are published in +
+ 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+`) under canonical packages (`com.google.maps.android.*`, `com.google.maps.android.clustering.*`, etc.). + +#### 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() + +// 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/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 6f3aa73a3..9c9b07388 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,8 +14,26 @@ * 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 + 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 { @@ -59,4 +77,27 @@ 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 { + Files.createSymbolicLink(m2Link.toPath(), realM2.toPath()) + } catch (_: Exception) {} + } + } + systemProperty("user.home", testHome.absolutePath) + 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/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 a17bcc2f1..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) } @@ -67,6 +68,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 +80,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/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/ClusterManagerFlows.kt b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.kt new file mode 100644 index 000000000..51fd21d4e --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/clustering/ClusterManagerFlows.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.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. + * + * **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 { + 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. + * + * **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 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/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 15356bfb7..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() } @@ -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) { @@ -1034,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 @@ -1042,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 @@ -1050,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 @@ -1058,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. @@ -1259,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 c36d6fa5c..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. @@ -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) { @@ -963,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 @@ -971,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 @@ -979,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 @@ -987,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. @@ -1123,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 ed2633829..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. @@ -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) { @@ -972,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 @@ -980,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 @@ -988,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 @@ -996,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. @@ -1130,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/geometry/PointExtensions.kt b/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.kt new file mode 100644 index 000000000..211f253fa --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/geometry/PointExtensions.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.geometry + +import com.google.maps.android.geometry.Point + +/** + * Returns the x value of this Point. + * + * e.g. + * + * ``` + * val (x, _) = point + * ``` + */ +public operator fun Point.component1(): Double = this.x + +/** + * Returns the y value of this Point. + * + * e.g. + * + * ``` + * val (_, y) = point + */ +public 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..53283208d --- /dev/null +++ b/clustering/src/main/java/com/google/maps/android/ktx/utils/geometry/Point.kt @@ -0,0 +1,26 @@ +/* + * 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 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 operator fun Point.component2(): Double = this.canonicalComponent2() 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/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/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/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..b531d6347 --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/clustering/ClusterManagerTest.kt @@ -0,0 +1,134 @@ +/* + * 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("DEPRECATION") + +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.async +import kotlinx.coroutines.flow.first +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 deferred = async { + clusterManager.clusterClickEvents().first() + } + advanceUntilIdle() + verify(clusterManager).setOnClusterClickListener(clusterClickListener.capture()) + clusterClickListener.value.onClusterClick(cluster) + assertThat(deferred.await()).isEqualTo(cluster) + } + + @Test + public fun testClusterItemClickEvents(): Unit = runTest { + val deferred = async { + clusterManager.clusterItemClickEvents().first() + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemClickListener(clusterItemClickListener.capture()) + clusterItemClickListener.value.onClusterItemClick(clusterItem) + assertThat(deferred.await()).isEqualTo(clusterItem) + } + + @Test + public fun testClusterInfoWindowClickEvents(): Unit = runTest { + val deferred = async { + clusterManager.clusterInfoWindowClickEvents().first() + } + advanceUntilIdle() + verify(clusterManager).setOnClusterInfoWindowClickListener(clusterInfoWindowClickListener.capture()) + clusterInfoWindowClickListener.value.onClusterInfoWindowClick(cluster) + assertThat(deferred.await()).isEqualTo(cluster) + } + + @Test + public fun testClusterInfoWindowLongClickEvents(): Unit = runTest { + val deferred = async { + clusterManager.clusterInfoWindowLongClickEvents().first() + } + advanceUntilIdle() + verify(clusterManager).setOnClusterInfoWindowLongClickListener(clusterInfoWindowLongClickListener.capture()) + clusterInfoWindowLongClickListener.value.onClusterInfoWindowLongClick(cluster) + assertThat(deferred.await()).isEqualTo(cluster) + } + + @Test + public fun testClusterItemInfoWindowClickEvents(): Unit = runTest { + val deferred = async { + clusterManager.clusterItemInfoWindowClickEvents().first() + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemInfoWindowClickListener(clusterItemInfoWindowClickListener.capture()) + clusterItemInfoWindowClickListener.value.onClusterItemInfoWindowClick(clusterItem) + assertThat(deferred.await()).isEqualTo(clusterItem) + } + + @Test + public fun testClusterItemInfoWindowLongClickEvents(): Unit = runTest { + val deferred = async { + clusterManager.clusterItemInfoWindowLongClickEvents().first() + } + advanceUntilIdle() + verify(clusterManager).setOnClusterItemInfoWindowLongClickListener(clusterItemInfoWindowLongClickListener.capture()) + clusterItemInfoWindowLongClickListener.value.onClusterItemInfoWindowLongClick(clusterItem) + assertThat(deferred.await()).isEqualTo(clusterItem) + } +} 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..d4e402ed3 --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/ktx/utils/geometry/PointTest.kt @@ -0,0 +1,46 @@ +/* + * 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("DEPRECATION") + +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/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 7445fcc7e..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) } @@ -79,11 +80,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/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/GeoJson.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt new file mode 100644 index 000000000..3619a0b1e --- /dev/null +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJson.kt @@ -0,0 +1,71 @@ +/* + * 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.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 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 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/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/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/Kml.kt b/data/src/main/java/com/google/maps/android/data/kml/Kml.kt new file mode 100644 index 000000000..6df061988 --- /dev/null +++ b/data/src/main/java/com/google/maps/android/data/kml/Kml.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.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 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 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/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/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 + + + -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/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..bef287ed0 --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/KtxExtensionsDemoActivity.kt @@ -0,0 +1,131 @@ +/* + * 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.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.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 + +/** + * 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 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(lifecycleOwner, mapView) { + // 1. Canonical awaitMapsSdkInitialized() coroutine suspension + context.awaitMapsSdkInitialized(MapsInitializer.Renderer.LATEST) + + // 2. Canonical awaitMap() coroutine suspension + val googleMap: GoogleMap = mapView.awaitMap() + + val sydney = LatLng(-33.852, 151.211) + + // 3. 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 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() + } + } + } + + Box(modifier = Modifier.fillMaxSize()) { + AndroidView(factory = { mapView }) + } + } +} 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/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/libs.versions.toml b/gradle/libs.versions.toml index 8215f38f2..86d941418 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.4.0" navigation-sdk = "7.9.0" desugar-jdk-libs = "2.1.5" @@ -42,9 +43,9 @@ 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 = "5.4.0" mockk = "1.14.11" -robolectric = "4.17" +robolectric = "4.16.1" truth = "1.4.5" uiautomator = "2.4.0" @@ -55,6 +56,7 @@ lint = "32.3.1" # --- Gradle Plugins --- # Versions for Gradle plugins used in the build process. +binary-compatibility-validator = "0.18.2" dokka-gradle-plugin = "2.2.0" gradle = "9.3.1" gradleMavenPublishPlugin = "0.37.0" @@ -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" } @@ -107,7 +110,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 = "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" } @@ -125,6 +128,7 @@ testutils = { module = "com.android.tools:testutils", version.ref = "lint" } # --- Gradle Plugins --- # Dependencies for custom or third-party Gradle plugins used in the build. +binary-compatibility-validator = { module = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin", version.ref = "binary-compatibility-validator" } dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka-gradle-plugin" } gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } gradle-maven-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradleMavenPublishPlugin" } @@ -133,4 +137,5 @@ secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.se [plugins] # --- Core Gradle Plugins --- +binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/heatmaps/api/heatmaps.api b/heatmaps/api/heatmaps.api new file mode 100644 index 000000000..3109eac30 --- /dev/null +++ b/heatmaps/api/heatmaps.api @@ -0,0 +1,98 @@ +public final class com/google/maps/android/heatmaps/Gradient { + public static final field Companion Lcom/google/maps/android/heatmaps/Gradient$Companion; + public fun ([I[F)V + public fun ([I[FI)V + public synthetic fun ([I[FIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun generateColorMap ()[I + public final fun generateColorMap (D)[I + public static synthetic fun generateColorMap$default (Lcom/google/maps/android/heatmaps/Gradient;DILjava/lang/Object;)[I + public final fun getColorMapSize ()I + public final fun getColors ()[I + public final fun getStartPoints ()[F + public static final fun interpolateColor (IIF)I +} + +public final class com/google/maps/android/heatmaps/Gradient$Companion { + public final fun interpolateColor (IIF)I +} + +public final class com/google/maps/android/heatmaps/HeatmapKt { + public static final fun heatmapTileProviderWithData (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DD)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static synthetic fun heatmapTileProviderWithData$default (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DDILjava/lang/Object;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static final fun heatmapTileProviderWithWeightedData (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DD)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static synthetic fun heatmapTileProviderWithWeightedData$default (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DDILjava/lang/Object;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static final fun toWeightedLatLng (Lcom/google/android/gms/maps/model/LatLng;D)Lcom/google/maps/android/heatmaps/WeightedLatLng; + public static synthetic fun toWeightedLatLng$default (Lcom/google/android/gms/maps/model/LatLng;DILjava/lang/Object;)Lcom/google/maps/android/heatmaps/WeightedLatLng; +} + +public final class com/google/maps/android/heatmaps/HeatmapTileProvider : com/google/android/gms/maps/model/TileProvider { + public static final field Companion Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Companion; + public static final field DEFAULT_GRADIENT Lcom/google/maps/android/heatmaps/Gradient; + public static final field DEFAULT_OPACITY D + public static final field DEFAULT_RADIUS I + public static final field MAX_RADIUS I + public static final field MIN_RADIUS I + public synthetic fun (Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun convolve ([[D[D)[[D + public static final fun generateKernel (ID)[D + public static final fun getBounds (Ljava/util/Collection;)Lcom/google/maps/android/geometry/Bounds; + public fun getTile (III)Lcom/google/android/gms/maps/model/Tile; + public final fun setData (Ljava/util/Collection;)V + public final fun setGradient (Lcom/google/maps/android/heatmaps/Gradient;)V + public final fun setMaxIntensity (D)V + public final fun setOpacity (D)V + public final fun setRadius (I)V + public final fun setWeightedData (Ljava/util/Collection;)V + public final fun updateData (Ljava/util/Collection;)V + public final fun updateLatLngs (Ljava/util/Collection;)V +} + +public final class com/google/maps/android/heatmaps/HeatmapTileProvider$Builder { + public fun ()V + public final fun build ()Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public final fun data (Ljava/util/Collection;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder; + public final fun gradient (Lcom/google/maps/android/heatmaps/Gradient;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder; + public final fun maxIntensity (D)Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder; + public final fun opacity (D)Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder; + public final fun radius (I)Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder; + public final fun weightedData (Ljava/util/Collection;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider$Builder; +} + +public final class com/google/maps/android/heatmaps/HeatmapTileProvider$Companion { + public final fun convolve ([[D[D)[[D + public final fun generateKernel (ID)[D + public final fun getBounds (Ljava/util/Collection;)Lcom/google/maps/android/geometry/Bounds; +} + +public final class com/google/maps/android/heatmaps/WeightedLatLng : com/google/maps/android/quadtree/PointQuadTree$Item { + public static final field Companion Lcom/google/maps/android/heatmaps/WeightedLatLng$Companion; + public static final field DEFAULT_INTENSITY D + public fun (Lcom/google/android/gms/maps/model/LatLng;)V + public fun (Lcom/google/android/gms/maps/model/LatLng;D)V + public synthetic fun (Lcom/google/android/gms/maps/model/LatLng;DILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/maps/android/geometry/Point;D)V + public final fun component1 ()Lcom/google/android/gms/maps/model/LatLng; + public final fun component2 ()Lcom/google/maps/android/geometry/Point; + public final fun component3 ()D + public final fun copy (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/maps/android/geometry/Point;D)Lcom/google/maps/android/heatmaps/WeightedLatLng; + public static synthetic fun copy$default (Lcom/google/maps/android/heatmaps/WeightedLatLng;Lcom/google/android/gms/maps/model/LatLng;Lcom/google/maps/android/geometry/Point;DILjava/lang/Object;)Lcom/google/maps/android/heatmaps/WeightedLatLng; + public fun equals (Ljava/lang/Object;)Z + public final fun getIntensity ()D + public final fun getLatLng ()Lcom/google/android/gms/maps/model/LatLng; + public fun getPoint ()Lcom/google/maps/android/geometry/Point; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/heatmaps/WeightedLatLng$Companion { +} + +public final class com/google/maps/android/ktx/utils/heatmaps/HeatmapKt { + public static final fun heatmapTileProviderWithData (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DD)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static synthetic fun heatmapTileProviderWithData$default (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DDILjava/lang/Object;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static final fun heatmapTileProviderWithWeightedData (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DD)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static synthetic fun heatmapTileProviderWithWeightedData$default (Ljava/util/Collection;ILcom/google/maps/android/heatmaps/Gradient;DDILjava/lang/Object;)Lcom/google/maps/android/heatmaps/HeatmapTileProvider; + public static final fun toWeightedLatLng (Lcom/google/android/gms/maps/model/LatLng;D)Lcom/google/maps/android/heatmaps/WeightedLatLng; + public static synthetic fun toWeightedLatLng$default (Lcom/google/android/gms/maps/model/LatLng;DILjava/lang/Object;)Lcom/google/maps/android/heatmaps/WeightedLatLng; +} + diff --git a/heatmaps/build.gradle.kts b/heatmaps/build.gradle.kts index 08b2f81e1..7a928a85a 100644 --- a/heatmaps/build.gradle.kts +++ b/heatmaps/build.gradle.kts @@ -48,6 +48,7 @@ android { } kotlin { + explicitApi() compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } @@ -77,11 +78,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/heatmaps/src/main/java/com/google/maps/android/heatmaps/Gradient.kt b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Gradient.kt index bcdcedc05..136932451 100644 --- a/heatmaps/src/main/java/com/google/maps/android/heatmaps/Gradient.kt +++ b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Gradient.kt @@ -29,12 +29,12 @@ import java.util.HashMap * @param colorMapSize The size of the colorMap to be generated by the Gradient. * Default value is 1000. */ -class Gradient +public class Gradient @JvmOverloads - constructor( - val colors: IntArray, - val startPoints: FloatArray, - val colorMapSize: Int = DEFAULT_COLOR_MAP_SIZE, + public constructor( + public val colors: IntArray, + public val startPoints: FloatArray, + public val colorMapSize: Int = DEFAULT_COLOR_MAP_SIZE, ) { private data class ColorInterval( val color1: Int, @@ -97,7 +97,7 @@ class Gradient * @return An integer array representing the color map, where each element is a color integer. */ @JvmOverloads - fun generateColorMap(opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY): IntArray { + public fun generateColorMap(opacity: Double = HeatmapTileProvider.DEFAULT_OPACITY): IntArray { val colorIntervals = generateColorIntervals() val colorMap = IntArray(colorMapSize) var interval = colorIntervals[0] @@ -125,7 +125,7 @@ class Gradient return colorMap } - companion object { + public companion object { private const val DEFAULT_COLOR_MAP_SIZE = 1000 /** @@ -138,7 +138,7 @@ class Gradient * @return Color associated with x2 */ @JvmStatic - fun interpolateColor( + public fun interpolateColor( color1: Int, color2: Int, ratio: Float, 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..f232203b6 --- /dev/null +++ b/heatmaps/src/main/java/com/google/maps/android/heatmaps/Heatmap.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.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 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 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 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/heatmaps/HeatmapTileProvider.kt b/heatmaps/src/main/java/com/google/maps/android/heatmaps/HeatmapTileProvider.kt index d643e2bc6..d3434a473 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 @@ -31,7 +31,7 @@ import kotlin.math.pow /** * Tile provider that creates heatmap tiles. */ -class HeatmapTileProvider private constructor( +public class HeatmapTileProvider private constructor( builder: Builder, ) : TileProvider { private var data: Collection @@ -62,7 +62,7 @@ class HeatmapTileProvider private constructor( /** * Builder class for the HeatmapTileProvider. */ - class Builder { + public class Builder public constructor() { internal var weightedData: Collection? = null internal var radius = DEFAULT_RADIUS internal var gradient = DEFAULT_GRADIENT @@ -75,7 +75,7 @@ class HeatmapTileProvider private constructor( * @param latLngs A collection of LatLngs. * @return This builder. */ - fun data(latLngs: Collection): Builder { + public fun data(latLngs: Collection): Builder { weightedData(wrapData(latLngs)) require(weightedData?.isNotEmpty() == true) { "No input points." } return this @@ -87,7 +87,7 @@ class HeatmapTileProvider private constructor( * @param weightedData A collection of WeightedLatLngs. * @return This builder. */ - fun weightedData(weightedData: Collection): Builder { + public fun weightedData(weightedData: Collection): Builder { this.weightedData = weightedData require(this.weightedData?.isNotEmpty() == true) { "No input points." } return this @@ -99,7 +99,7 @@ class HeatmapTileProvider private constructor( * @param radius The radius. Must be between 10 and 50, inclusive. * @return This builder. */ - fun radius(radius: Int): Builder { + public fun radius(radius: Int): Builder { this.radius = radius require(this.radius in MIN_RADIUS..MAX_RADIUS) { "Radius not within bounds." } return this @@ -111,7 +111,7 @@ class HeatmapTileProvider private constructor( * @param gradient The gradient to use. * @return This builder. */ - fun gradient(gradient: Gradient): Builder { + public fun gradient(gradient: Gradient): Builder { this.gradient = gradient return this } @@ -122,7 +122,7 @@ class HeatmapTileProvider private constructor( * @param opacity The opacity. Must be between 0 and 1, inclusive. * @return This builder. */ - fun opacity(opacity: Double): Builder { + public fun opacity(opacity: Double): Builder { this.opacity = opacity require(this.opacity in 0.0..1.0) { "Opacity must be in range [0, 1]" } return this @@ -134,7 +134,7 @@ class HeatmapTileProvider private constructor( * @param intensity The maximum intensity. * @return This builder. */ - fun maxIntensity(intensity: Double): Builder { + public fun maxIntensity(intensity: Double): Builder { this.intensity = intensity return this } @@ -144,7 +144,7 @@ class HeatmapTileProvider private constructor( * * @return A new HeatmapTileProvider. */ - fun build(): HeatmapTileProvider { + public fun build(): HeatmapTileProvider { require( this.weightedData?.isNotEmpty() == true, ) { "No input data: you must use either .data or .weightedData before building." } @@ -153,7 +153,7 @@ class HeatmapTileProvider private constructor( } @Deprecated("Use updateData(Collection) instead.", ReplaceWith("updateData(data)")) - fun setWeightedData(data: Collection) { + public fun setWeightedData(data: Collection) { updateData(data) } @@ -166,7 +166,7 @@ class HeatmapTileProvider private constructor( * * @param data The new collection of [WeightedLatLng] points. */ - fun updateData(data: Collection) { + public fun updateData(data: Collection) { this.data = data require(this.data.isNotEmpty()) { "No input points." } this.bounds = getBounds(this.data) @@ -178,7 +178,7 @@ class HeatmapTileProvider private constructor( } @Deprecated("Use updateLatLngs(Collection) instead.", ReplaceWith("updateLatLngs(latLngs)")) - fun setData(latLngs: Collection) { + public fun setData(latLngs: Collection) { updateLatLngs(latLngs) } @@ -191,32 +191,34 @@ class HeatmapTileProvider private constructor( * * @param latLngs The new collection of [LatLng] points. */ - fun updateLatLngs(latLngs: Collection) { + public fun updateLatLngs(latLngs: Collection) { updateData(wrapData(latLngs)) } - fun setGradient(gradient: Gradient) { + public fun setGradient(gradient: Gradient) { this.gradient = gradient this.colorMap = gradient.generateColorMap(this.opacity) } - fun setRadius(radius: Int) { + public 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) { + public fun setOpacity(opacity: Double) { + require(opacity in 0.0..1.0) { "Opacity must be in range [0, 1]" } this.opacity = opacity setGradient(this.gradient) } - fun setMaxIntensity(intensity: Double) { + public fun setMaxIntensity(intensity: Double) { this.customMaxIntensity = intensity updateData(this.data) } - override fun getTile( + public override fun getTile( x: Int, y: Int, zoom: Int, @@ -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) } @@ -299,22 +302,22 @@ class HeatmapTileProvider private constructor( return maxIntensityArray } - companion object { - const val DEFAULT_RADIUS = 20 - const val DEFAULT_OPACITY = 0.7 + public companion object { + public const val DEFAULT_RADIUS: Int = 20 + public const val DEFAULT_OPACITY: Double = 0.7 private val DEFAULT_GRADIENT_COLORS = intArrayOf(Color.rgb(102, 225, 0), Color.rgb(255, 0, 0)) private val DEFAULT_GRADIENT_START_POINTS = floatArrayOf(0.2f, 1f) @JvmField - val DEFAULT_GRADIENT = Gradient(DEFAULT_GRADIENT_COLORS, DEFAULT_GRADIENT_START_POINTS) + public val DEFAULT_GRADIENT: Gradient = Gradient(DEFAULT_GRADIENT_COLORS, DEFAULT_GRADIENT_START_POINTS) internal const val WORLD_WIDTH = 1.0 private const val TILE_DIM = 512 private const val SCREEN_SIZE = 1280 private const val DEFAULT_MIN_ZOOM = 5 private const val DEFAULT_MAX_ZOOM = 11 private const val MAX_ZOOM_LEVEL = 22 - const val MIN_RADIUS = 10 - const val MAX_RADIUS = 50 + public const val MIN_RADIUS: Int = 10 + public const val MAX_RADIUS: Int = 50 private data class Vector( val x: Int, @@ -331,7 +334,7 @@ class HeatmapTileProvider private constructor( } @JvmStatic - fun getBounds(points: Collection): Bounds { + public fun getBounds(points: Collection): Bounds { val firstPoint = points.first().point var minX = firstPoint.x var maxX = firstPoint.x @@ -350,7 +353,7 @@ class HeatmapTileProvider private constructor( } @JvmStatic - fun generateKernel( + public fun generateKernel( radius: Int, sd: Double, ): DoubleArray { @@ -362,7 +365,7 @@ class HeatmapTileProvider private constructor( } @JvmStatic - fun convolve( + public fun convolve( grid: Array, kernel: DoubleArray, ): Array { diff --git a/heatmaps/src/main/java/com/google/maps/android/heatmaps/WeightedLatLng.kt b/heatmaps/src/main/java/com/google/maps/android/heatmaps/WeightedLatLng.kt index 8601dd082..25831cf2e 100644 --- a/heatmaps/src/main/java/com/google/maps/android/heatmaps/WeightedLatLng.kt +++ b/heatmaps/src/main/java/com/google/maps/android/heatmaps/WeightedLatLng.kt @@ -20,10 +20,10 @@ import com.google.maps.android.geometry.Point import com.google.maps.android.projection.SphericalMercatorProjection import com.google.maps.android.quadtree.PointQuadTree -data class WeightedLatLng( - val latLng: LatLng, +public data class WeightedLatLng( + public val latLng: LatLng, override val point: Point, - val intensity: Double, + public val intensity: Double, ) : PointQuadTree.Item { /** * Constructor that uses default value for intensity @@ -31,14 +31,14 @@ data class WeightedLatLng( * @param latLng LatLng to add to wrapper */ @JvmOverloads - constructor(latLng: LatLng, intensity: Double = DEFAULT_INTENSITY) : this( + public constructor(latLng: LatLng, intensity: Double = DEFAULT_INTENSITY) : this( latLng, sProjection.toPoint(latLng), if (intensity >= 0) intensity else DEFAULT_INTENSITY, ) - companion object { - const val DEFAULT_INTENSITY = 1.0 + public companion object { + public const val DEFAULT_INTENSITY: Double = 1.0 private val sProjection = SphericalMercatorProjection(HeatmapTileProvider.WORLD_WIDTH) } } 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..3d62d32c5 --- /dev/null +++ b/heatmaps/src/main/java/com/google/maps/android/ktx/utils/heatmaps/Heatmap.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.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 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 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 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/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/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..d993ad869 --- /dev/null +++ b/heatmaps/src/test/java/com/google/maps/android/ktx/utils/heatmaps/HeatmapTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("DEPRECATION") + +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/api/library.api b/library/api/library.api new file mode 100644 index 000000000..ca48c5952 --- /dev/null +++ b/library/api/library.api @@ -0,0 +1,768 @@ +public abstract class com/google/maps/android/CameraEvent { +} + +public final class com/google/maps/android/CameraIdleEvent : com/google/maps/android/CameraEvent { + public static final field INSTANCE Lcom/google/maps/android/CameraIdleEvent; +} + +public final class com/google/maps/android/CameraMoveCanceledEvent : com/google/maps/android/CameraEvent { + public static final field INSTANCE Lcom/google/maps/android/CameraMoveCanceledEvent; +} + +public final class com/google/maps/android/CameraMoveEvent : com/google/maps/android/CameraEvent { + public static final field INSTANCE Lcom/google/maps/android/CameraMoveEvent; +} + +public final class com/google/maps/android/CameraMoveStartedEvent : com/google/maps/android/CameraEvent { + public fun (I)V + public final fun component1 ()I + public final fun copy (I)Lcom/google/maps/android/CameraMoveStartedEvent; + public static synthetic fun copy$default (Lcom/google/maps/android/CameraMoveStartedEvent;IILjava/lang/Object;)Lcom/google/maps/android/CameraMoveStartedEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/FlexiblePoint { + public fun (DD)V + 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/FlexiblePoint; + public static synthetic fun copy$default (Lcom/google/maps/android/FlexiblePoint;DDLjava/lang/Double;ILjava/lang/Object;)Lcom/google/maps/android/FlexiblePoint; + public fun equals (Ljava/lang/Object;)Z + public final fun getLatitude ()D + public final fun getLongitude ()D + public final fun getThirdDimensionValue ()Ljava/lang/Double; + public fun hashCode ()I + public final fun toLatLng ()Lcom/google/android/gms/maps/model/LatLng; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/FlexiblePolyline { + public fun (Ljava/util/List;ILcom/google/maps/android/ThirdDimension;I)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()I + public final fun component3 ()Lcom/google/maps/android/ThirdDimension; + public final fun component4 ()I + public final fun copy (Ljava/util/List;ILcom/google/maps/android/ThirdDimension;I)Lcom/google/maps/android/FlexiblePolyline; + public static synthetic fun copy$default (Lcom/google/maps/android/FlexiblePolyline;Ljava/util/List;ILcom/google/maps/android/ThirdDimension;IILjava/lang/Object;)Lcom/google/maps/android/FlexiblePolyline; + public fun equals (Ljava/lang/Object;)Z + public final fun getPoints ()Ljava/util/List; + public final fun getPrecision ()I + public final fun getThirdDimension ()Lcom/google/maps/android/ThirdDimension; + public final fun getThirdDimensionPrecision ()I + public fun hashCode ()I + public final fun toLatLngs ()Ljava/util/List; + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/FlexiblePolylineUtil { + public static final field INSTANCE Lcom/google/maps/android/FlexiblePolylineUtil; + public static final fun decode (Ljava/lang/String;)Lcom/google/maps/android/FlexiblePolyline; + public static final fun decodeToLatLngs (Ljava/lang/String;)Ljava/util/List; + public static final fun encode (Ljava/util/List;)Ljava/lang/String; + public static final fun encode (Ljava/util/List;I)Ljava/lang/String; + public static final fun encode (Ljava/util/List;ILcom/google/maps/android/ThirdDimension;)Ljava/lang/String; + public static final fun encode (Ljava/util/List;ILcom/google/maps/android/ThirdDimension;I)Ljava/lang/String; + public static synthetic fun encode$default (Ljava/util/List;ILcom/google/maps/android/ThirdDimension;IILjava/lang/Object;)Ljava/lang/String; + public static final fun encodeLatLngs (Ljava/util/List;)Ljava/lang/String; + public static final fun encodeLatLngs (Ljava/util/List;I)Ljava/lang/String; + public static synthetic fun encodeLatLngs$default (Ljava/util/List;IILjava/lang/Object;)Ljava/lang/String; + public static final fun getThirdDimension (Ljava/lang/String;)Lcom/google/maps/android/ThirdDimension; +} + +public final class com/google/maps/android/GoogleMapKt { + public static final fun addCircle (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Circle; + public static final fun addGroundOverlay (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/GroundOverlay; + public static final fun addMarker (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Marker; + public static final fun addPolygon (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polygon; + public static final fun addPolyline (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polyline; + public static final fun addTileOverlay (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/TileOverlay; + public static final fun awaitAnimateCamera (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/android/gms/maps/CameraUpdate;Ljava/lang/Integer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitAnimateCamera$default (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/android/gms/maps/CameraUpdate;Ljava/lang/Integer;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun awaitMapLoad (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun awaitSnapshot (Lcom/google/android/gms/maps/GoogleMap;Landroid/graphics/Bitmap;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitSnapshot$default (Lcom/google/android/gms/maps/GoogleMap;Landroid/graphics/Bitmap;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun buildGoogleMapOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/GoogleMapOptions; + public static final fun cameraEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraIdleEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraMoveCanceledEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraMoveEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraMoveStartedEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun circleClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun groundOverlayClicks (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun indoorStateChangeEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowCloseEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowLongClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun mapClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun mapLongClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun markerClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun markerDragEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun myLocationButtonClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun myLocationClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun poiClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun polygonClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun polylineClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/IndoorBuildingFocusedEvent : com/google/maps/android/IndoorChangeEvent { + public static final field INSTANCE Lcom/google/maps/android/IndoorBuildingFocusedEvent; +} + +public abstract class com/google/maps/android/IndoorChangeEvent { +} + +public final class com/google/maps/android/IndoorLevelActivatedEvent : com/google/maps/android/IndoorChangeEvent { + public fun (Lcom/google/android/gms/maps/model/IndoorBuilding;)V + public final fun component1 ()Lcom/google/android/gms/maps/model/IndoorBuilding; + public final fun copy (Lcom/google/android/gms/maps/model/IndoorBuilding;)Lcom/google/maps/android/IndoorLevelActivatedEvent; + public static synthetic fun copy$default (Lcom/google/maps/android/IndoorLevelActivatedEvent;Lcom/google/android/gms/maps/model/IndoorBuilding;ILjava/lang/Object;)Lcom/google/maps/android/IndoorLevelActivatedEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getBuilding ()Lcom/google/android/gms/maps/model/IndoorBuilding; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/LatLngKt { + public static final fun component1 (Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun component2 (Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun computeSphericalOffsetOrigin (Lcom/google/android/gms/maps/model/LatLng;DD)Lcom/google/android/gms/maps/model/LatLng; + public static final fun containsLocation (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;Z)Z + public static final fun isClosedPolygon (Ljava/util/List;)Z + public static final fun isLocationOnPath (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZD)Z + public static synthetic fun isLocationOnPath$default (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZDILjava/lang/Object;)Z + public static final fun isOnEdge (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZD)Z + public static synthetic fun isOnEdge$default (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZDILjava/lang/Object;)Z + public static final fun latLngListEncode (Ljava/util/List;)Ljava/lang/String; + public static final fun simplify (Ljava/util/List;D)Ljava/util/List; + public static final fun sphericalDistance (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun sphericalHeading (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun sphericalPathLength (Ljava/util/List;)D + public static final fun sphericalPolygonArea (Ljava/util/List;)D + public static final fun sphericalPolygonSignedArea (Ljava/util/List;)D + public static final fun toLatLngList (Ljava/lang/String;)Ljava/util/List; + public static final fun withSphericalLinearInterpolation (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;D)Lcom/google/android/gms/maps/model/LatLng; + public static final fun withSphericalOffset (Lcom/google/android/gms/maps/model/LatLng;DD)Lcom/google/android/gms/maps/model/LatLng; +} + +public final class com/google/maps/android/MapFragmentKt { + public static final fun awaitMap (Lcom/google/android/gms/maps/MapFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/MapViewKt { + public static final fun awaitMap (Lcom/google/android/gms/maps/MapView;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface annotation class com/google/maps/android/MapsExperimentalFeature : java/lang/annotation/Annotation { +} + +public final class com/google/maps/android/MapsInitializerKt { + public static final fun awaitMapsSdkInitialized (Landroid/content/Context;Lcom/google/android/gms/maps/MapsInitializer$Renderer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitMapsSdkInitialized$default (Landroid/content/Context;Lcom/google/android/gms/maps/MapsInitializer$Renderer;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/google/maps/android/MarkerDragEndEvent : com/google/maps/android/OnMarkerDragEvent { + public fun (Lcom/google/android/gms/maps/model/Marker;)V + public final fun component1 ()Lcom/google/android/gms/maps/model/Marker; + public final fun copy (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/MarkerDragEndEvent; + public static synthetic fun copy$default (Lcom/google/maps/android/MarkerDragEndEvent;Lcom/google/android/gms/maps/model/Marker;ILjava/lang/Object;)Lcom/google/maps/android/MarkerDragEndEvent; + public fun equals (Ljava/lang/Object;)Z + public fun getMarker ()Lcom/google/android/gms/maps/model/Marker; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/MarkerDragEvent : com/google/maps/android/OnMarkerDragEvent { + public fun (Lcom/google/android/gms/maps/model/Marker;)V + public final fun component1 ()Lcom/google/android/gms/maps/model/Marker; + public final fun copy (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/MarkerDragEvent; + public static synthetic fun copy$default (Lcom/google/maps/android/MarkerDragEvent;Lcom/google/android/gms/maps/model/Marker;ILjava/lang/Object;)Lcom/google/maps/android/MarkerDragEvent; + public fun equals (Ljava/lang/Object;)Z + public fun getMarker ()Lcom/google/android/gms/maps/model/Marker; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/MarkerDragStartEvent : com/google/maps/android/OnMarkerDragEvent { + public fun (Lcom/google/android/gms/maps/model/Marker;)V + public final fun component1 ()Lcom/google/android/gms/maps/model/Marker; + public final fun copy (Lcom/google/android/gms/maps/model/Marker;)Lcom/google/maps/android/MarkerDragStartEvent; + public static synthetic fun copy$default (Lcom/google/maps/android/MarkerDragStartEvent;Lcom/google/android/gms/maps/model/Marker;ILjava/lang/Object;)Lcom/google/maps/android/MarkerDragStartEvent; + public fun equals (Ljava/lang/Object;)Z + public fun getMarker ()Lcom/google/android/gms/maps/model/Marker; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/MathUtil { + public static final field EARTH_RADIUS D + public static final field INSTANCE Lcom/google/maps/android/MathUtil; + public static final fun arcHav (D)D + public static final fun clamp (DDD)D + public static final fun hav (D)D + public static final fun havDistance (DDD)D + public static final fun havFromSin (D)D + public static final fun inverseMercator (D)D + public static final fun mercator (D)D + public static final fun mod (DD)D + public static final fun sinFromHav (D)D + public static final fun sinSumFromHav (DD)D + public static final fun wrap (DDD)D +} + +public abstract interface annotation class com/google/maps/android/MoveStartedReason : java/lang/annotation/Annotation { +} + +public abstract class com/google/maps/android/OnMarkerDragEvent { + public abstract fun getMarker ()Lcom/google/android/gms/maps/model/Marker; +} + +public final class com/google/maps/android/PolyUtil { + public static final field DEFAULT_TOLERANCE D + public static final field INSTANCE Lcom/google/maps/android/PolyUtil; + public static final fun containsLocation (DDLjava/util/List;Z)Z + public static final fun containsLocation (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;Z)Z + public static final fun decode (Ljava/lang/String;)Ljava/util/List; + public static final fun distanceToLine (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun encode (Ljava/util/List;)Ljava/lang/String; + public static final fun isClosedPolygon (Ljava/util/List;)Z + public static final fun isLocationOnEdge (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;Z)Z + public static final fun isLocationOnEdge (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZD)Z + public static synthetic fun isLocationOnEdge$default (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZDILjava/lang/Object;)Z + public static final fun isLocationOnPath (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;Z)Z + public static final fun isLocationOnPath (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZD)Z + public static synthetic fun isLocationOnPath$default (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZDILjava/lang/Object;)Z + public static final fun locationIndexOnEdgeOrPath (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZZD)I + public static final fun locationIndexOnPath (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;Z)I + public static final fun locationIndexOnPath (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZD)I + public static synthetic fun locationIndexOnPath$default (Lcom/google/android/gms/maps/model/LatLng;Ljava/util/List;ZDILjava/lang/Object;)I + public static final fun simplify (Ljava/util/List;D)Ljava/util/List; +} + +public final class com/google/maps/android/PolygonKt { + public static final fun contains (Lcom/google/android/gms/maps/model/Polygon;Lcom/google/android/gms/maps/model/LatLng;)Z + public static final fun getArea (Lcom/google/android/gms/maps/model/Polygon;)D + public static final fun getSignedArea (Lcom/google/android/gms/maps/model/Polygon;)D + public static final fun isOnEdge (Lcom/google/android/gms/maps/model/Polygon;Lcom/google/android/gms/maps/model/LatLng;D)Z + public static synthetic fun isOnEdge$default (Lcom/google/android/gms/maps/model/Polygon;Lcom/google/android/gms/maps/model/LatLng;DILjava/lang/Object;)Z +} + +public final class com/google/maps/android/PolylineKt { + public static final fun contains (Lcom/google/android/gms/maps/model/Polyline;Lcom/google/android/gms/maps/model/LatLng;D)Z + public static synthetic fun contains$default (Lcom/google/android/gms/maps/model/Polyline;Lcom/google/android/gms/maps/model/LatLng;DILjava/lang/Object;)Z + public static final fun getSphericalPathLength (Lcom/google/android/gms/maps/model/Polyline;)D +} + +public final class com/google/maps/android/ResponseStreetView { + public fun (Lcom/google/maps/android/Status;)V + public final fun component1 ()Lcom/google/maps/android/Status; + public final fun copy (Lcom/google/maps/android/Status;)Lcom/google/maps/android/ResponseStreetView; + public static synthetic fun copy$default (Lcom/google/maps/android/ResponseStreetView;Lcom/google/maps/android/Status;ILjava/lang/Object;)Lcom/google/maps/android/ResponseStreetView; + public fun equals (Ljava/lang/Object;)Z + public final fun getStatus ()Lcom/google/maps/android/Status; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/google/maps/android/Source : java/lang/Enum { + public static final field DEFAULT Lcom/google/maps/android/Source; + public static final field OUTDOOR Lcom/google/maps/android/Source; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()Ljava/lang/String; + public final fun setValue (Ljava/lang/String;)V + public static fun valueOf (Ljava/lang/String;)Lcom/google/maps/android/Source; + public static fun values ()[Lcom/google/maps/android/Source; +} + +public final class com/google/maps/android/SphericalUtil { + public static final field INSTANCE Lcom/google/maps/android/SphericalUtil; + public static final fun computeAngleBetween (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun computeArea (Ljava/util/List;)D + public static final fun computeDistanceBetween (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun computeHeading (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun computeLength (Ljava/util/List;)D + public static final fun computeOffset (Lcom/google/android/gms/maps/model/LatLng;DD)Lcom/google/android/gms/maps/model/LatLng; + public static final fun computeOffsetOrigin (Lcom/google/android/gms/maps/model/LatLng;DD)Lcom/google/android/gms/maps/model/LatLng; + public static final fun computeSignedArea (Ljava/util/List;)D + public static final fun computeSignedArea (Ljava/util/List;D)D + public static final fun getPointOnPolyline (Ljava/util/List;D)Lcom/google/android/gms/maps/model/LatLng; + public static final fun getPolylinePrefix (Ljava/util/List;D)Ljava/util/List; + public static final fun interpolate (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;D)Lcom/google/android/gms/maps/model/LatLng; +} + +public final class com/google/maps/android/Status : java/lang/Enum { + public static final field INVALID_REQUEST Lcom/google/maps/android/Status; + public static final field NOT_FOUND Lcom/google/maps/android/Status; + public static final field OK Lcom/google/maps/android/Status; + public static final field OVER_QUERY_LIMIT Lcom/google/maps/android/Status; + public static final field REQUEST_DENIED Lcom/google/maps/android/Status; + public static final field UNKNOWN_ERROR Lcom/google/maps/android/Status; + public static final field ZERO_RESULTS Lcom/google/maps/android/Status; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/google/maps/android/Status; + public static fun values ()[Lcom/google/maps/android/Status; +} + +public final class com/google/maps/android/StreetViewJavaHelper { + public static final field INSTANCE Lcom/google/maps/android/StreetViewJavaHelper; + public static final fun fetchStreetViewData (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Lcom/google/maps/android/StreetViewJavaHelper$StreetViewCallback;)V +} + +public abstract interface class com/google/maps/android/StreetViewJavaHelper$StreetViewCallback { + public abstract fun onStreetViewError (Ljava/lang/Exception;)V + public abstract fun onStreetViewResult (Lcom/google/maps/android/Status;)V +} + +public final class com/google/maps/android/StreetViewPanoramaFragmentKt { + public static final fun awaitStreetViewPanorama (Lcom/google/android/gms/maps/StreetViewPanoramaFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/StreetViewPanoramaViewKt { + public static final fun awaitStreetViewPanorama (Lcom/google/android/gms/maps/StreetViewPanoramaView;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun cameraChangeEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; + public static final fun changeEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; + public static final fun clickEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; + public static final fun longClickEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/StreetViewUtils { + public static final field Companion Lcom/google/maps/android/StreetViewUtils$Companion; + public fun ()V +} + +public final class com/google/maps/android/StreetViewUtils$Companion { + public final fun fetchStreetViewData (Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Lcom/google/maps/android/Source;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun fetchStreetViewData$default (Lcom/google/maps/android/StreetViewUtils$Companion;Lcom/google/android/gms/maps/model/LatLng;Ljava/lang/String;Lcom/google/maps/android/Source;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/google/maps/android/SupportMapFragmentKt { + public static final fun awaitMap (Lcom/google/android/gms/maps/SupportMapFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/SupportStreetViewPanoramaFragmentKt { + public static final fun awaitStreetViewPanorama (Lcom/google/android/gms/maps/SupportStreetViewPanoramaFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/ThirdDimension : java/lang/Enum { + public static final field ABSENT Lcom/google/maps/android/ThirdDimension; + public static final field ALTITUDE Lcom/google/maps/android/ThirdDimension; + public static final field CUSTOM1 Lcom/google/maps/android/ThirdDimension; + public static final field CUSTOM2 Lcom/google/maps/android/ThirdDimension; + public static final field ELEVATION Lcom/google/maps/android/ThirdDimension; + public static final field LEVEL Lcom/google/maps/android/ThirdDimension; + public static final field RESERVED1 Lcom/google/maps/android/ThirdDimension; + public static final field RESERVED2 Lcom/google/maps/android/ThirdDimension; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Lcom/google/maps/android/ThirdDimension; + public static fun values ()[Lcom/google/maps/android/ThirdDimension; +} + +public class com/google/maps/android/collections/CircleManager : com/google/maps/android/collections/MapObjectManager, com/google/android/gms/maps/GoogleMap$OnCircleClickListener { + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public fun newCollection ()Lcom/google/maps/android/collections/CircleManager$Collection; + public synthetic fun newCollection ()Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun onCircleClick (Lcom/google/android/gms/maps/model/Circle;)V + public fun removeObjectFromMap (Lcom/google/android/gms/maps/model/Circle;)V + public synthetic fun removeObjectFromMap (Ljava/lang/Object;)V + public fun setVisible (Lcom/google/android/gms/maps/model/Circle;Z)V + public synthetic fun setVisible (Ljava/lang/Object;Z)V +} + +public class com/google/maps/android/collections/CircleManager$Collection : com/google/maps/android/collections/MapObjectManager$Collection { + public fun (Lcom/google/maps/android/collections/CircleManager;)V + public fun addAll (Ljava/util/Collection;)V + public fun addAll (Ljava/util/Collection;Z)V + public fun addCircle (Lcom/google/android/gms/maps/model/CircleOptions;)Lcom/google/android/gms/maps/model/Circle; + public fun getCircles ()Ljava/util/Collection; + public fun setOnCircleClickListener (Lcom/google/android/gms/maps/GoogleMap$OnCircleClickListener;)V +} + +public final class com/google/maps/android/collections/CircleManagerFlowsKt { + public static final fun addCircle (Lcom/google/maps/android/collections/CircleManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Circle; + public static final fun clickEvents (Lcom/google/maps/android/collections/CircleManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public class com/google/maps/android/collections/GroundOverlayManager : com/google/maps/android/collections/MapObjectManager, com/google/android/gms/maps/GoogleMap$OnGroundOverlayClickListener { + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public fun newCollection ()Lcom/google/maps/android/collections/GroundOverlayManager$Collection; + public synthetic fun newCollection ()Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun onGroundOverlayClick (Lcom/google/android/gms/maps/model/GroundOverlay;)V + public fun removeObjectFromMap (Lcom/google/android/gms/maps/model/GroundOverlay;)V + public synthetic fun removeObjectFromMap (Ljava/lang/Object;)V + public fun setVisible (Lcom/google/android/gms/maps/model/GroundOverlay;Z)V + public synthetic fun setVisible (Ljava/lang/Object;Z)V +} + +public class com/google/maps/android/collections/GroundOverlayManager$Collection : com/google/maps/android/collections/MapObjectManager$Collection { + public fun (Lcom/google/maps/android/collections/GroundOverlayManager;)V + public fun addAll (Ljava/util/Collection;)V + public fun addAll (Ljava/util/Collection;Z)V + public fun addGroundOverlay (Lcom/google/android/gms/maps/model/GroundOverlayOptions;)Lcom/google/android/gms/maps/model/GroundOverlay; + public fun getGroundOverlays ()Ljava/util/Collection; + public fun setOnGroundOverlayClickListener (Lcom/google/android/gms/maps/GoogleMap$OnGroundOverlayClickListener;)V +} + +public final class com/google/maps/android/collections/GroundOverlayManagerFlowsKt { + public static final fun addGroundOverlay (Lcom/google/maps/android/collections/GroundOverlayManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/GroundOverlay; + public static final fun clickEvents (Lcom/google/maps/android/collections/GroundOverlayManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public abstract class com/google/maps/android/collections/MapObjectManager { + protected final field mAllObjects Ljava/util/Map; + protected final field mMap Lcom/google/android/gms/maps/GoogleMap; + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public fun getCollection (Ljava/lang/String;)Lcom/google/maps/android/collections/MapObjectManager$Collection; + public abstract fun newCollection ()Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun newCollection (Ljava/lang/String;)Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun remove (Ljava/lang/Object;)Z + protected abstract fun removeObjectFromMap (Ljava/lang/Object;)V + protected fun setVisible (Ljava/lang/Object;Z)V +} + +public class com/google/maps/android/collections/MapObjectManager$Collection { + public fun (Lcom/google/maps/android/collections/MapObjectManager;)V + protected fun add (Ljava/lang/Object;)V + protected fun addAll (Ljava/util/Collection;Lkotlin/jvm/functions/Function1;)V + protected fun addAll (Ljava/util/Collection;ZLkotlin/jvm/functions/Function1;)V + protected fun checkAndAdd (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; + public fun clear ()V + protected fun getObjects ()Ljava/util/Collection; + public fun hideAll ()V + public fun remove (Ljava/lang/Object;)Z + public fun showAll ()V +} + +public class com/google/maps/android/collections/MarkerManager : com/google/maps/android/collections/MapObjectManager, com/google/android/gms/maps/GoogleMap$InfoWindowAdapter, com/google/android/gms/maps/GoogleMap$OnInfoWindowClickListener, com/google/android/gms/maps/GoogleMap$OnInfoWindowLongClickListener, com/google/android/gms/maps/GoogleMap$OnMarkerClickListener, com/google/android/gms/maps/GoogleMap$OnMarkerDragListener { + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public fun getInfoContents (Lcom/google/android/gms/maps/model/Marker;)Landroid/view/View; + public fun getInfoWindow (Lcom/google/android/gms/maps/model/Marker;)Landroid/view/View; + public synthetic fun newCollection ()Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun newCollection ()Lcom/google/maps/android/collections/MarkerManager$Collection; + public fun onInfoWindowClick (Lcom/google/android/gms/maps/model/Marker;)V + public fun onInfoWindowLongClick (Lcom/google/android/gms/maps/model/Marker;)V + public fun onMarkerClick (Lcom/google/android/gms/maps/model/Marker;)Z + public fun onMarkerDrag (Lcom/google/android/gms/maps/model/Marker;)V + public fun onMarkerDragEnd (Lcom/google/android/gms/maps/model/Marker;)V + public fun onMarkerDragStart (Lcom/google/android/gms/maps/model/Marker;)V + public fun removeObjectFromMap (Lcom/google/android/gms/maps/model/Marker;)V + public synthetic fun removeObjectFromMap (Ljava/lang/Object;)V + public fun setVisible (Lcom/google/android/gms/maps/model/Marker;Z)V + public synthetic fun setVisible (Ljava/lang/Object;Z)V +} + +public class com/google/maps/android/collections/MarkerManager$Collection : com/google/maps/android/collections/MapObjectManager$Collection { + public fun (Lcom/google/maps/android/collections/MarkerManager;)V + public fun addAll (Ljava/util/Collection;)V + public fun addAll (Ljava/util/Collection;Z)V + public fun addMarker (Lcom/google/android/gms/maps/model/AdvancedMarkerOptions;)Lcom/google/android/gms/maps/model/Marker; + public fun addMarker (Lcom/google/android/gms/maps/model/MarkerOptions;)Lcom/google/android/gms/maps/model/Marker; + public fun getMarkers ()Ljava/util/Collection; + public fun setInfoWindowAdapter (Lcom/google/android/gms/maps/GoogleMap$InfoWindowAdapter;)V + public fun setOnInfoWindowClickListener (Lcom/google/android/gms/maps/GoogleMap$OnInfoWindowClickListener;)V + public fun setOnInfoWindowLongClickListener (Lcom/google/android/gms/maps/GoogleMap$OnInfoWindowLongClickListener;)V + public fun setOnMarkerClickListener (Lcom/google/android/gms/maps/GoogleMap$OnMarkerClickListener;)V + public fun setOnMarkerDragListener (Lcom/google/android/gms/maps/GoogleMap$OnMarkerDragListener;)V +} + +public final class com/google/maps/android/collections/MarkerManagerFlowsKt { + public static final fun addMarker (Lcom/google/maps/android/collections/MarkerManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Marker; + public static final fun clickEvents (Lcom/google/maps/android/collections/MarkerManager$Collection;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowClickEvents (Lcom/google/maps/android/collections/MarkerManager$Collection;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowLongClickEvents (Lcom/google/maps/android/collections/MarkerManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public class com/google/maps/android/collections/PolygonManager : com/google/maps/android/collections/MapObjectManager, com/google/android/gms/maps/GoogleMap$OnPolygonClickListener { + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public synthetic fun newCollection ()Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun newCollection ()Lcom/google/maps/android/collections/PolygonManager$Collection; + public fun onPolygonClick (Lcom/google/android/gms/maps/model/Polygon;)V + public fun removeObjectFromMap (Lcom/google/android/gms/maps/model/Polygon;)V + public synthetic fun removeObjectFromMap (Ljava/lang/Object;)V + public fun setVisible (Lcom/google/android/gms/maps/model/Polygon;Z)V + public synthetic fun setVisible (Ljava/lang/Object;Z)V +} + +public class com/google/maps/android/collections/PolygonManager$Collection : com/google/maps/android/collections/MapObjectManager$Collection { + public fun (Lcom/google/maps/android/collections/PolygonManager;)V + public fun addAll (Ljava/util/Collection;)V + public fun addAll (Ljava/util/Collection;Z)V + public fun addPolygon (Lcom/google/android/gms/maps/model/PolygonOptions;)Lcom/google/android/gms/maps/model/Polygon; + public fun getPolygons ()Ljava/util/Collection; + public fun setOnPolygonClickListener (Lcom/google/android/gms/maps/GoogleMap$OnPolygonClickListener;)V +} + +public final class com/google/maps/android/collections/PolygonManagerFlowsKt { + public static final fun addPolygon (Lcom/google/maps/android/collections/PolygonManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polygon; + public static final fun clickEvents (Lcom/google/maps/android/collections/PolygonManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public class com/google/maps/android/collections/PolylineManager : com/google/maps/android/collections/MapObjectManager, com/google/android/gms/maps/GoogleMap$OnPolylineClickListener { + public fun (Lcom/google/android/gms/maps/GoogleMap;)V + public synthetic fun newCollection ()Lcom/google/maps/android/collections/MapObjectManager$Collection; + public fun newCollection ()Lcom/google/maps/android/collections/PolylineManager$Collection; + public fun onPolylineClick (Lcom/google/android/gms/maps/model/Polyline;)V + public fun removeObjectFromMap (Lcom/google/android/gms/maps/model/Polyline;)V + public synthetic fun removeObjectFromMap (Ljava/lang/Object;)V + public fun setVisible (Lcom/google/android/gms/maps/model/Polyline;Z)V + public synthetic fun setVisible (Ljava/lang/Object;Z)V +} + +public class com/google/maps/android/collections/PolylineManager$Collection : com/google/maps/android/collections/MapObjectManager$Collection { + public fun (Lcom/google/maps/android/collections/PolylineManager;)V + public fun addAll (Ljava/util/Collection;)V + public fun addAll (Ljava/util/Collection;Z)V + public fun addPolyline (Lcom/google/android/gms/maps/model/PolylineOptions;)Lcom/google/android/gms/maps/model/Polyline; + public fun getPolylines ()Ljava/util/Collection; + public fun setOnPolylineClickListener (Lcom/google/android/gms/maps/GoogleMap$OnPolylineClickListener;)V +} + +public final class com/google/maps/android/collections/PolylineManagerFlowsKt { + public static final fun addPolyline (Lcom/google/maps/android/collections/PolylineManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polyline; + public static final fun clickEvents (Lcom/google/maps/android/collections/PolylineManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/GoogleMapKt { + public static final fun addCircle (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Circle; + public static final fun addGroundOverlay (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/GroundOverlay; + public static final fun addMarker (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Marker; + public static final fun addPolygon (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polygon; + public static final fun addPolyline (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polyline; + public static final fun addTileOverlay (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/TileOverlay; + public static final fun awaitAnimateCamera (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/android/gms/maps/CameraUpdate;Ljava/lang/Integer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitAnimateCamera$default (Lcom/google/android/gms/maps/GoogleMap;Lcom/google/android/gms/maps/CameraUpdate;Ljava/lang/Integer;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun awaitMapLoad (Lcom/google/android/gms/maps/GoogleMap;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun awaitSnapshot (Lcom/google/android/gms/maps/GoogleMap;Landroid/graphics/Bitmap;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitSnapshot$default (Lcom/google/android/gms/maps/GoogleMap;Landroid/graphics/Bitmap;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun buildGoogleMapOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/GoogleMapOptions; + public static final fun cameraEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraIdleEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraMoveCanceledEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraMoveEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun cameraMoveStartedEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun circleClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun groundOverlayClicks (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun indoorStateChangeEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowCloseEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowLongClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun mapClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun mapLongClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun markerClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun markerDragEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun myLocationButtonClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun myLocationClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun poiClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun polygonClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; + public static final fun polylineClickEvents (Lcom/google/android/gms/maps/GoogleMap;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/MapFragmentKt { + public static final fun awaitMap (Lcom/google/android/gms/maps/MapFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/ktx/MapViewKt { + public static final fun awaitMap (Lcom/google/android/gms/maps/MapView;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface annotation class com/google/maps/android/ktx/MapsExperimentalFeature : java/lang/annotation/Annotation { +} + +public final class com/google/maps/android/ktx/MapsInitializerKt { + public static final fun awaitMapsSdkInitialized (Landroid/content/Context;Lcom/google/android/gms/maps/MapsInitializer$Renderer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitMapsSdkInitialized$default (Landroid/content/Context;Lcom/google/android/gms/maps/MapsInitializer$Renderer;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/google/maps/android/ktx/StreetViewPanoramaFragmentKt { + public static final fun awaitStreetViewPanorama (Lcom/google/android/gms/maps/StreetViewPanoramaFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/ktx/StreetViewPanoramaViewKt { + public static final fun awaitStreetViewPanorama (Lcom/google/android/gms/maps/StreetViewPanoramaView;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun cameraChangeEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; + public static final fun changeEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; + public static final fun clickEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; + public static final fun longClickEvents (Lcom/google/android/gms/maps/StreetViewPanorama;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/SupportMapFragmentKt { + public static final fun awaitMap (Lcom/google/android/gms/maps/SupportMapFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/ktx/SupportStreetViewPanoramaFragmentKt { + public static final fun awaitStreetViewPanorama (Lcom/google/android/gms/maps/SupportStreetViewPanoramaFragment;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/google/maps/android/ktx/model/CameraPositionKt { + public static final fun cameraPosition (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/CameraPosition; +} + +public final class com/google/maps/android/ktx/model/CircleOptionsKt { + public static final fun circleOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/CircleOptions; +} + +public final class com/google/maps/android/ktx/model/GroundOverlayOptionsKt { + public static final fun groundOverlayOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/GroundOverlayOptions; +} + +public final class com/google/maps/android/ktx/model/MarkerOptionsKt { + public static final fun markerOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/MarkerOptions; +} + +public final class com/google/maps/android/ktx/model/PolygonOptionsKt { + public static final fun polygonOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/PolygonOptions; +} + +public final class com/google/maps/android/ktx/model/PolylineOptionsKt { + public static final fun polylineOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/PolylineOptions; +} + +public final class com/google/maps/android/ktx/model/StreetViewPanoramaCameraKt { + public static final fun streetViewPanoramaCamera (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/StreetViewPanoramaCamera; +} + +public final class com/google/maps/android/ktx/model/StreetViewPanoramaOrientationKt { + public static final fun streetViewPanoramaOrientation (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/StreetViewPanoramaOrientation; +} + +public final class com/google/maps/android/ktx/model/TileOverlayOptionsKt { + public static final fun tileOverlayOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/TileOverlayOptions; +} + +public final class com/google/maps/android/ktx/utils/LatLngKt { + public static final fun component1 (Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun component2 (Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun computeSphericalOffsetOrigin (Lcom/google/android/gms/maps/model/LatLng;DD)Lcom/google/android/gms/maps/model/LatLng; + public static final fun containsLocation (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;Z)Z + public static final fun isClosedPolygon (Ljava/util/List;)Z + public static final fun isLocationOnPath (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZD)Z + public static synthetic fun isLocationOnPath$default (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZDILjava/lang/Object;)Z + public static final fun isOnEdge (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZD)Z + public static synthetic fun isOnEdge$default (Ljava/util/List;Lcom/google/android/gms/maps/model/LatLng;ZDILjava/lang/Object;)Z + public static final fun latLngListEncode (Ljava/util/List;)Ljava/lang/String; + public static final fun simplify (Ljava/util/List;D)Ljava/util/List; + public static final fun sphericalDistance (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun sphericalHeading (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)D + public static final fun sphericalPathLength (Ljava/util/List;)D + public static final fun sphericalPolygonArea (Ljava/util/List;)D + public static final fun sphericalPolygonSignedArea (Ljava/util/List;)D + public static final fun toLatLngList (Ljava/lang/String;)Ljava/util/List; + public static final fun withSphericalLinearInterpolation (Lcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;D)Lcom/google/android/gms/maps/model/LatLng; + public static final fun withSphericalOffset (Lcom/google/android/gms/maps/model/LatLng;DD)Lcom/google/android/gms/maps/model/LatLng; +} + +public final class com/google/maps/android/ktx/utils/PolygonKt { + public static final fun contains (Lcom/google/android/gms/maps/model/Polygon;Lcom/google/android/gms/maps/model/LatLng;)Z + public static final fun getArea (Lcom/google/android/gms/maps/model/Polygon;)D + public static final fun getSignedArea (Lcom/google/android/gms/maps/model/Polygon;)D + public static final fun isOnEdge (Lcom/google/android/gms/maps/model/Polygon;Lcom/google/android/gms/maps/model/LatLng;D)Z + public static synthetic fun isOnEdge$default (Lcom/google/android/gms/maps/model/Polygon;Lcom/google/android/gms/maps/model/LatLng;DILjava/lang/Object;)Z +} + +public final class com/google/maps/android/ktx/utils/PolylineKt { + public static final fun contains (Lcom/google/android/gms/maps/model/Polyline;Lcom/google/android/gms/maps/model/LatLng;D)Z + public static synthetic fun contains$default (Lcom/google/android/gms/maps/model/Polyline;Lcom/google/android/gms/maps/model/LatLng;DILjava/lang/Object;)Z + public static final fun getSphericalPathLength (Lcom/google/android/gms/maps/model/Polyline;)D +} + +public final class com/google/maps/android/ktx/utils/collection/CircleManagerKt { + public static final fun addCircle (Lcom/google/maps/android/collections/CircleManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Circle; + public static final fun clickEvents (Lcom/google/maps/android/collections/CircleManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/collection/GroundOverlayManagerKt { + public static final fun addGroundOverlay (Lcom/google/maps/android/collections/GroundOverlayManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/GroundOverlay; + public static final fun clickEvents (Lcom/google/maps/android/collections/GroundOverlayManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/collection/MarkerManagerKt { + public static final fun addMarker (Lcom/google/maps/android/collections/MarkerManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Marker; + public static final fun clickEvents (Lcom/google/maps/android/collections/MarkerManager$Collection;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowClickEvents (Lcom/google/maps/android/collections/MarkerManager$Collection;)Lkotlinx/coroutines/flow/Flow; + public static final fun infoWindowLongClickEvents (Lcom/google/maps/android/collections/MarkerManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/collection/PolygonManagerKt { + public static final fun addPolygon (Lcom/google/maps/android/collections/PolygonManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polygon; + public static final fun clickEvents (Lcom/google/maps/android/collections/PolygonManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/collection/PolylineManagerKt { + public static final fun addPolyline (Lcom/google/maps/android/collections/PolylineManager$Collection;Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/Polyline; + public static final fun clickEvents (Lcom/google/maps/android/collections/PolylineManager$Collection;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/location/FusedLocationProviderKt { + public static final fun fusedLocationEvents (Lcom/google/android/gms/location/FusedLocationProviderClient;JFILandroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun fusedLocationEvents$default (Lcom/google/android/gms/location/FusedLocationProviderClient;JFILandroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static final fun locationEvents (Lcom/google/android/gms/location/FusedLocationProviderClient;Lcom/google/android/gms/location/LocationRequest;Landroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun locationEvents$default (Lcom/google/android/gms/location/FusedLocationProviderClient;Lcom/google/android/gms/location/LocationRequest;Landroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/ktx/utils/location/LocationManagerKt { + public static final fun coarseLocationEvents (Landroid/location/LocationManager;JFLandroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun coarseLocationEvents$default (Landroid/location/LocationManager;JFLandroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static final fun fineLocationEvents (Landroid/location/LocationManager;JFLandroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun fineLocationEvents$default (Landroid/location/LocationManager;JFLandroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/location/FusedLocationProviderKt { + public static final fun fusedLocationEvents (Lcom/google/android/gms/location/FusedLocationProviderClient;JFILandroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun fusedLocationEvents$default (Lcom/google/android/gms/location/FusedLocationProviderClient;JFILandroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static final fun locationEvents (Lcom/google/android/gms/location/FusedLocationProviderClient;Lcom/google/android/gms/location/LocationRequest;Landroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun locationEvents$default (Lcom/google/android/gms/location/FusedLocationProviderClient;Lcom/google/android/gms/location/LocationRequest;Landroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/location/LocationManagerKt { + public static final fun coarseLocationEvents (Landroid/location/LocationManager;JFLandroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun coarseLocationEvents$default (Landroid/location/LocationManager;JFLandroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static final fun fineLocationEvents (Landroid/location/LocationManager;JFLandroid/os/Looper;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun fineLocationEvents$default (Landroid/location/LocationManager;JFLandroid/os/Looper;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + +public final class com/google/maps/android/model/CameraPositionKt { + public static final fun cameraPosition (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/CameraPosition; +} + +public final class com/google/maps/android/model/CircleOptionsKt { + public static final fun circleOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/CircleOptions; +} + +public final class com/google/maps/android/model/GroundOverlayOptionsKt { + public static final fun groundOverlayOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/GroundOverlayOptions; +} + +public final class com/google/maps/android/model/MarkerOptionsKt { + public static final fun markerOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/MarkerOptions; +} + +public final class com/google/maps/android/model/PolygonOptionsKt { + public static final fun polygonOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/PolygonOptions; +} + +public final class com/google/maps/android/model/PolylineOptionsKt { + public static final fun polylineOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/PolylineOptions; +} + +public final class com/google/maps/android/model/StreetViewPanoramaCameraKt { + public static final fun streetViewPanoramaCamera (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/StreetViewPanoramaCamera; +} + +public final class com/google/maps/android/model/StreetViewPanoramaOrientationKt { + public static final fun streetViewPanoramaOrientation (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/StreetViewPanoramaOrientation; +} + +public final class com/google/maps/android/model/TileOverlayOptionsKt { + public static final fun tileOverlayOptions (Lkotlin/jvm/functions/Function1;)Lcom/google/android/gms/maps/model/TileOverlayOptions; +} + +public final class com/google/maps/android/utils/meta/AttributionId { + public static final field INSTANCE Lcom/google/maps/android/utils/meta/AttributionId; + public static final field VALUE Ljava/lang/String; +} + diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 88119e578..a6631a17b 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -47,6 +47,7 @@ android { } kotlin { + explicitApi() compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } @@ -63,11 +64,14 @@ android { dependencies { api(libs.play.services.maps) + 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) @@ -76,11 +80,8 @@ 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) } tasks.register("instrumentTest") { diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro index e69de29bb..6411008cd 100644 --- a/library/consumer-rules.pro +++ b/library/consumer-rules.pro @@ -0,0 +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 diff --git a/library/src/main/java/com/google/maps/android/FlexiblePolyline.kt b/library/src/main/java/com/google/maps/android/FlexiblePolyline.kt index 76cc2ef70..94f92ecb0 100644 --- a/library/src/main/java/com/google/maps/android/FlexiblePolyline.kt +++ b/library/src/main/java/com/google/maps/android/FlexiblePolyline.kt @@ -24,8 +24,8 @@ import kotlin.math.round * * @property value The numeric type identifier used on the wire. */ -enum class ThirdDimension( - val value: Int, +public enum class ThirdDimension( + public val value: Int, ) { /** No third value is present; the polyline is purely two dimensional. */ ABSENT(0), @@ -75,19 +75,19 @@ enum class ThirdDimension( * @property thirdDimensionValue The third value for this point, or `null` when the polyline * carries no third dimension. The unit is defined by the polyline's [ThirdDimension]. */ -data class FlexiblePoint +public data class FlexiblePoint @JvmOverloads - constructor( - val latitude: Double, - val longitude: Double, - val thirdDimensionValue: Double? = null, + public constructor( + public val latitude: Double, + public val longitude: Double, + public val thirdDimensionValue: Double? = null, ) { /** * Returns this point as a [LatLng], discarding [thirdDimensionValue]. * * @return The two dimensional position of this point. */ - fun toLatLng(): LatLng = LatLng(latitude, longitude) + public fun toLatLng(): LatLng = LatLng(latitude, longitude) } /** @@ -99,11 +99,11 @@ data class FlexiblePoint * @property thirdDimensionPrecision The number of decimal places preserved for the third * value, or `0` when [thirdDimension] is [ThirdDimension.ABSENT]. */ -data class FlexiblePolyline( - val points: List, - val precision: Int, - val thirdDimension: ThirdDimension, - val thirdDimensionPrecision: Int, +public data class FlexiblePolyline( + public val points: List, + public val precision: Int, + public val thirdDimension: ThirdDimension, + public val thirdDimensionPrecision: Int, ) { /** * Returns the decoded points as [LatLng] values ready to pass to @@ -111,7 +111,7 @@ data class FlexiblePolyline( * * @return The two dimensional path described by this polyline. */ - fun toLatLngs(): List = points.map(FlexiblePoint::toLatLng) + public fun toLatLngs(): List = points.map(FlexiblePoint::toLatLng) } /** @@ -131,7 +131,7 @@ data class FlexiblePolyline( * * Written from the published specification. */ -object FlexiblePolylineUtil { +public object FlexiblePolylineUtil { /** The format version written to, and expected in, the header. */ private const val FORMAT_VERSION = 1L @@ -165,7 +165,7 @@ object FlexiblePolylineUtil { */ @JvmStatic @JvmOverloads - fun encodeLatLngs( + public fun encodeLatLngs( path: List, precision: Int = 7, ): String = @@ -196,7 +196,7 @@ object FlexiblePolylineUtil { */ @JvmStatic @JvmOverloads - fun encode( + public fun encode( points: List, precision: Int = 7, thirdDimension: ThirdDimension = ThirdDimension.ABSENT, @@ -263,7 +263,7 @@ object FlexiblePolylineUtil { * version, contains a character outside the alphabet, or ends part way through a point. */ @JvmStatic - fun decode(encoded: String): FlexiblePolyline { + public fun decode(encoded: String): FlexiblePolyline { require(encoded.isNotEmpty()) { "encoded polyline must not be empty" } val cursor = Cursor(encoded) @@ -319,7 +319,7 @@ object FlexiblePolylineUtil { * @throws IllegalArgumentException if [encoded] is not a well formed flexible polyline. */ @JvmStatic - fun decodeToLatLngs(encoded: String): List = decode(encoded).toLatLngs() + public fun decodeToLatLngs(encoded: String): List = decode(encoded).toLatLngs() /** * Returns the third dimension declared by an encoded polyline without decoding its points. @@ -329,7 +329,7 @@ object FlexiblePolylineUtil { * @throws IllegalArgumentException if [encoded] has no readable header. */ @JvmStatic - fun getThirdDimension(encoded: String): ThirdDimension { + public fun getThirdDimension(encoded: String): ThirdDimension { require(encoded.isNotEmpty()) { "encoded polyline must not be empty" } val cursor = Cursor(encoded) val version = decodeUnsignedVarint(cursor) 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..3491d0bad --- /dev/null +++ b/library/src/main/java/com/google/maps/android/GoogleMap.kt @@ -0,0 +1,594 @@ +/* + * 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 + +/** + * Annotation indicating the reason a camera move started. + * See [GoogleMap.OnCameraMoveStartedListener]. + */ +@IntDef( + GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE, + GoogleMap.OnCameraMoveStartedListener.REASON_API_ANIMATION, + GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION +) +@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() + +/** + * 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, or `null` to use the Maps SDK default duration + */ +public suspend fun GoogleMap.awaitAnimateCamera( + cameraUpdate: CameraUpdate, + durationMs: Int? = null +): Unit = + suspendCancellableCoroutine { continuation -> + val callback = object : GoogleMap.CancelableCallback { + override fun onFinish() { + if (continuation.isActive) { + continuation.resume(Unit) + } + } + + override fun onCancel() { + if (continuation.isActive) { + continuation.cancel() + } + } + } + if (durationMs != null) { + animateCamera(cameraUpdate, durationMs, callback) + } else { + animateCamera(cameraUpdate, callback) + } + } + +/** + * A suspending function that awaits for the map to be loaded. Uses + * [GoogleMap.setOnMapLoadedCallback]. + */ +public suspend fun GoogleMap.awaitMapLoad(): Unit = + suspendCancellableCoroutine { continuation -> + setOnMapLoadedCallback { + if (continuation.isActive) { + continuation.resume(Unit) + } + } + continuation.invokeOnCancellation { + setOnMapLoadedCallback(null) + } + } + +/** + * 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 fun GoogleMap.awaitSnapshot(bitmap: Bitmap? = null): Bitmap? = + suspendCancellableCoroutine { continuation -> + snapshot( + { + if (continuation.isActive) { + 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 [GroundOverlay] + */ +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 [TileOverlay] + */ +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..d7f7c09aa --- /dev/null +++ b/library/src/main/java/com/google/maps/android/LatLng.kt @@ -0,0 +1,213 @@ +/* + * 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.maps.android.PolyUtil +import com.google.maps.android.SphericalUtil + +/** + * Returns the [LatLng.latitude] of this [LatLng]. + * + * e.g. + * ``` + * val (lat, _) = latLng + * ``` + */ +public operator fun LatLng.component1(): Double = this.latitude + +/** + * Returns the [LatLng.longitude] of this [LatLng]. + * + * e.g. + * ``` + * val (_, lng) = latLng + * ``` + */ +public 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 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 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 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 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 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 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 fun List.isClosedPolygon(): Boolean = PolyUtil.isClosedPolygon(this) + +/** + * Computes the length of this path on Earth. + * + * @return the length of this path in meters + */ +public fun List.sphericalPathLength(): Double = SphericalUtil.computeLength(this) + +/** + * Computes the area under a closed path on Earth. + * + * @return the area in square meters + */ +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 + * determine the orientation of the path. + * + * @return the signed area in square meters + */ +public 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 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 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 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 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 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..43572e5dc --- /dev/null +++ b/library/src/main/java/com/google/maps/android/MapFragment.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 + +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 fun MapFragment.awaitMap(): GoogleMap = + suspendCancellableCoroutine { continuation -> + getMapAsync { + 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 new file mode 100644 index 000000000..4d5c1d72c --- /dev/null +++ b/library/src/main/java/com/google/maps/android/MapView.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 + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapView +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +/** + * 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 fun MapView.awaitMap(): GoogleMap = + suspendCancellableCoroutine { continuation -> + getMapAsync { + if (continuation.isActive) { + 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/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/MapsInitializer.kt new file mode 100644 index 000000000..6348caeea --- /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 kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * 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 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. + * + * @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 fun Context.awaitMapsSdkInitialized( + preferredRenderer: MapsInitializer.Renderer? = null +): MapsInitializer.Renderer = + suspendCancellableCoroutine { continuation -> + val status = MapsInitializer.initialize(this, preferredRenderer) { renderer -> + if (continuation.isActive) { + continuation.resume(renderer) + } + } + if (continuation.isActive && status != ConnectionResult.SUCCESS) { + continuation.resumeWithException(GooglePlayServicesNotAvailableException(status)) + } + } diff --git a/library/src/main/java/com/google/maps/android/MathUtil.kt b/library/src/main/java/com/google/maps/android/MathUtil.kt index 62728d59b..5566cdc07 100644 --- a/library/src/main/java/com/google/maps/android/MathUtil.kt +++ b/library/src/main/java/com/google/maps/android/MathUtil.kt @@ -28,18 +28,18 @@ import kotlin.math.tan /** * Utility functions that are used my both PolyUtil and SphericalUtil. */ -object MathUtil { +public object MathUtil { /** * The earth's radius, in meters. * Mean radius as defined by IUGG. */ - const val EARTH_RADIUS = 6_371_009.0 + public const val EARTH_RADIUS: Double = 6_371_009.0 /** * Restrict x to the range [low, high]. */ @JvmStatic - fun clamp( + public fun clamp( x: Double, low: Double, high: Double, @@ -60,7 +60,7 @@ object MathUtil { * @param max The maximum. */ @JvmStatic - fun wrap( + public fun wrap( n: Double, min: Double, max: Double, @@ -73,7 +73,7 @@ object MathUtil { * @param m The modulus. */ @JvmStatic - fun mod( + public fun mod( x: Double, m: Double, ): Double = (x % m + m) % m @@ -83,7 +83,7 @@ object MathUtil { * See http://en.wikipedia.org/wiki/Mercator_projection . */ @JvmStatic - fun mercator(lat: Double): Double { + public fun mercator(lat: Double): Double { if (lat > Math.PI / 2 - 1e-9) { return Double.POSITIVE_INFINITY } @@ -97,14 +97,14 @@ object MathUtil { * Returns latitude from mercator Y. */ @JvmStatic - fun inverseMercator(y: Double): Double = 2 * atan(exp(y)) - PI / 2 + public fun inverseMercator(y: Double): Double = 2 * atan(exp(y)) - PI / 2 /** * Returns haversine(angle-in-radians). * hav(x) == (1 - cos(x)) / 2 == sin(x / 2)^2. */ @JvmStatic - fun hav(x: Double): Double { + public fun hav(x: Double): Double { val sinHalf = sin(x * 0.5) return sinHalf * sinHalf } @@ -115,22 +115,22 @@ object MathUtil { * The argument must be in [0, 1], and the result is positive. */ @JvmStatic - fun arcHav(x: Double): Double = 2 * asin(sqrt(x)) + public fun arcHav(x: Double): Double = 2 * asin(sqrt(x)) // Given h==hav(x), returns sin(abs(x)). @JvmStatic - fun sinFromHav(h: Double): Double = 2 * sqrt(h * (1 - h)) + public fun sinFromHav(h: Double): Double = 2 * sqrt(h * (1 - h)) // Returns hav(asin(x)). @JvmStatic - fun havFromSin(x: Double): Double { + public fun havFromSin(x: Double): Double { val x2 = x * x return x2 / (1 + sqrt(1 - x2)) * .5 } // Returns sin(arcHav(x) + arcHav(y)). @JvmStatic - fun sinSumFromHav( + public fun sinSumFromHav( x: Double, y: Double, ): Double { @@ -143,7 +143,7 @@ object MathUtil { * Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit sphere. */ @JvmStatic - fun havDistance( + public fun havDistance( lat1: Double, lat2: Double, dLng: Double, diff --git a/library/src/main/java/com/google/maps/android/PolyUtil.kt b/library/src/main/java/com/google/maps/android/PolyUtil.kt index d2ab855c0..77f5be345 100644 --- a/library/src/main/java/com/google/maps/android/PolyUtil.kt +++ b/library/src/main/java/com/google/maps/android/PolyUtil.kt @@ -43,8 +43,8 @@ import kotlin.math.tan * and they operate on {@link LatLng} objects. The calculations can be performed * using either geodesic (great circle) or rhumb (loxodromic) paths. */ -object PolyUtil { - const val DEFAULT_TOLERANCE = 0.1 // meters +public object PolyUtil { + public const val DEFAULT_TOLERANCE: Double = 0.1 // meters /** * Computes whether the given point lies inside the specified polygon. @@ -60,7 +60,7 @@ object PolyUtil { * @return `true` if the point is inside the polygon, `false` otherwise. */ @JvmStatic - fun containsLocation( + public fun containsLocation( point: LatLng, polygon: List, geodesic: Boolean, @@ -71,7 +71,7 @@ object PolyUtil { * longitude as separate arguments. */ @JvmStatic - fun containsLocation( + public fun containsLocation( latitude: Double, longitude: Double, polygon: List, @@ -120,7 +120,7 @@ object PolyUtil { */ @JvmStatic @JvmOverloads - fun isLocationOnEdge( + public fun isLocationOnEdge( point: LatLng, polygon: List, geodesic: Boolean, @@ -141,7 +141,7 @@ object PolyUtil { */ @JvmStatic @JvmOverloads - fun isLocationOnPath( + public fun isLocationOnPath( point: LatLng, polyline: List, geodesic: Boolean, @@ -177,7 +177,7 @@ object PolyUtil { */ @JvmStatic @JvmOverloads - fun locationIndexOnPath( + public fun locationIndexOnPath( point: LatLng, poly: List, geodesic: Boolean, @@ -201,7 +201,7 @@ object PolyUtil { * poly.size()-2 if between poly[poly.size() - 2] and poly[poly.size() - 1] */ @JvmStatic - fun locationIndexOnEdgeOrPath( + public fun locationIndexOnEdgeOrPath( point: LatLng, poly: List, closed: Boolean, @@ -294,7 +294,7 @@ object PolyUtil { * @return a simplified poly produced by the Douglas-Peucker algorithm */ @JvmStatic - fun simplify( + public fun simplify( poly: List, tolerance: Double, ): List { @@ -399,7 +399,7 @@ object PolyUtil { * points are the same), and false if it is not */ @JvmStatic - fun isClosedPolygon(poly: List): Boolean = poly.isNotEmpty() && poly.first() == poly.last() + public fun isClosedPolygon(poly: List): Boolean = poly.isNotEmpty() && poly.first() == poly.last() /** * Computes the distance on the sphere between the point p and the line segment start to end. @@ -410,7 +410,7 @@ object PolyUtil { * @return the distance in meters (assuming spherical earth) */ @JvmStatic - fun distanceToLine( + public fun distanceToLine( p: LatLng, start: LatLng, end: LatLng, @@ -452,7 +452,7 @@ object PolyUtil { * Decodes an encoded path string into a sequence of LatLngs. */ @JvmStatic - fun decode(encodedPath: String): List { + public fun decode(encodedPath: String): List { val len = encodedPath.length val path = mutableListOf() var index = 0 @@ -489,7 +489,7 @@ object PolyUtil { * Encodes a sequence of LatLngs into an encoded path string. */ @JvmStatic - fun encode(path: List): String { + public fun encode(path: List): String { var lastLat: Long = 0 var lastLng: Long = 0 val result = StringBuilder() 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..7e7cb0934 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/Polygon.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.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 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 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 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 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..dc8769e8a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/Polyline.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 + +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 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 val Polyline.sphericalPathLength: Double + get() = SphericalUtil.computeLength(this.points) diff --git a/library/src/main/java/com/google/maps/android/SphericalUtil.kt b/library/src/main/java/com/google/maps/android/SphericalUtil.kt index 504b4f693..2603605ab 100644 --- a/library/src/main/java/com/google/maps/android/SphericalUtil.kt +++ b/library/src/main/java/com/google/maps/android/SphericalUtil.kt @@ -29,7 +29,7 @@ import kotlin.math.sin import kotlin.math.sqrt import kotlin.math.tan -object SphericalUtil { +public object SphericalUtil { /** * Returns the heading from one LatLng to another LatLng. Headings are * expressed in degrees clockwise from North within the range [-180,180). @@ -37,7 +37,7 @@ object SphericalUtil { * @return The heading in degrees clockwise from north. */ @JvmStatic - fun computeHeading( + public fun computeHeading( from: LatLng, to: LatLng, ): Double { @@ -64,7 +64,7 @@ object SphericalUtil { * @param heading The heading in degrees clockwise from north. */ @JvmStatic - fun computeOffset( + public fun computeOffset( from: LatLng, distance: Double, heading: Double, @@ -100,7 +100,7 @@ object SphericalUtil { * @param heading The heading in degrees clockwise from north. */ @JvmStatic - fun computeOffsetOrigin( + public fun computeOffsetOrigin( to: LatLng, distance: Double, heading: Double, @@ -152,7 +152,7 @@ object SphericalUtil { * @return The interpolated LatLng. */ @JvmStatic - fun interpolate( + public fun interpolate( from: LatLng, to: LatLng, fraction: Double, @@ -203,7 +203,7 @@ object SphericalUtil { * on the unit sphere. */ @JvmStatic - fun computeAngleBetween( + public fun computeAngleBetween( from: LatLng, to: LatLng, ): Double = @@ -218,7 +218,7 @@ object SphericalUtil { * Returns the distance between two LatLngs, in meters. */ @JvmStatic - fun computeDistanceBetween( + public fun computeDistanceBetween( from: LatLng, to: LatLng, ): Double = computeAngleBetween(from, to) * EARTH_RADIUS @@ -227,7 +227,7 @@ object SphericalUtil { * Returns the length of the given path, in meters, on Earth. */ @JvmStatic - fun computeLength(path: List): Double { + public fun computeLength(path: List): Double { if (path.size < 2) { return 0.0 } @@ -253,7 +253,7 @@ object SphericalUtil { * @return The path's area in square meters. */ @JvmStatic - fun computeArea(path: List): Double = abs(computeSignedArea(path)) + public fun computeArea(path: List): Double = abs(computeSignedArea(path)) /** * Returns the signed area of a closed path on Earth. The sign of the area may be used to @@ -264,7 +264,7 @@ object SphericalUtil { * @return The loop's area in square meters. */ @JvmStatic - fun computeSignedArea(path: List): Double = computeSignedArea(path, EARTH_RADIUS) + public fun computeSignedArea(path: List): Double = computeSignedArea(path, EARTH_RADIUS) /** * Returns the signed area of a closed path on a sphere of given radius. @@ -272,7 +272,7 @@ object SphericalUtil { * Used by SphericalUtilTest. */ @JvmStatic - fun computeSignedArea( + public fun computeSignedArea( path: List, radius: Double, ): Double { @@ -324,7 +324,7 @@ object SphericalUtil { * the percentage is outside the range [0, 1] */ @JvmStatic - fun getPointOnPolyline( + public fun getPointOnPolyline( polyline: List, percentage: Double, ): LatLng? = getPolylinePrefix(polyline, percentage).lastOrNull() @@ -342,7 +342,7 @@ object SphericalUtil { * is empty or the percentage is outside the range [0, 1] */ @JvmStatic - fun getPolylinePrefix( + public fun getPolylinePrefix( polyline: List, percentage: Double, ): List { diff --git a/library/src/main/java/com/google/maps/android/StreetViewJavaHelper.kt b/library/src/main/java/com/google/maps/android/StreetViewJavaHelper.kt index 7d45c0699..d1d69134b 100644 --- a/library/src/main/java/com/google/maps/android/StreetViewJavaHelper.kt +++ b/library/src/main/java/com/google/maps/android/StreetViewJavaHelper.kt @@ -23,24 +23,24 @@ import kotlinx.coroutines.launch /** * A helper object to call the suspend function `fetchStreetViewData` from Java. */ -object StreetViewJavaHelper { +public object StreetViewJavaHelper { /** * A callback interface to receive the result of the Street View data fetch. */ - interface StreetViewCallback { + public interface StreetViewCallback { /** * Called when the Street View data is fetched successfully. * * @param status The status of the Street View data. */ - fun onStreetViewResult(status: Status) + public fun onStreetViewResult(status: Status) /** * Called when there is an error fetching the Street View data. * * @param e The exception that occurred. */ - fun onStreetViewError(e: Exception) + public fun onStreetViewError(e: Exception) } /** @@ -51,7 +51,7 @@ object StreetViewJavaHelper { * @param callback The callback to receive the result. */ @JvmStatic - fun fetchStreetViewData( + public fun fetchStreetViewData( latLng: LatLng, apiKey: String, callback: StreetViewCallback, 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..008d81119 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.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 + +/** + * 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 fun StreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCancellableCoroutine { continuation -> + getStreetViewPanoramaAsync { + 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 new file mode 100644 index 000000000..caee00a39 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/StreetViewPanoramaView.kt @@ -0,0 +1,104 @@ +/* + * 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 kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +/** + * 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 fun StreetViewPanoramaView.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCancellableCoroutine { continuation -> + getStreetViewPanoramaAsync { + if (continuation.isActive) { + 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/StreetViewUtil.kt b/library/src/main/java/com/google/maps/android/StreetViewUtil.kt index 3db600867..0f80bc97d 100644 --- a/library/src/main/java/com/google/maps/android/StreetViewUtil.kt +++ b/library/src/main/java/com/google/maps/android/StreetViewUtil.kt @@ -28,8 +28,8 @@ import java.net.URL /** * Utility functions for StreetView */ -class StreetViewUtils { - companion object { +public class StreetViewUtils { + public companion object { /** * This function will check whether a location is available on StreetView or not. * @@ -41,7 +41,7 @@ class StreetViewUtils { * @return A Status value specifying if the location is available on Street View or not, * whether the used key is a right one, or any other error. */ - suspend fun fetchStreetViewData( + public suspend fun fetchStreetViewData( latLng: LatLng, apiKey: String, source: Source = Source.DEFAULT, @@ -86,11 +86,11 @@ class StreetViewUtils { } } -data class ResponseStreetView( - val status: Status, +public data class ResponseStreetView( + public val status: Status, ) -enum class Status { +public enum class Status { OK, ZERO_RESULTS, NOT_FOUND, @@ -100,8 +100,8 @@ enum class Status { UNKNOWN_ERROR, } -enum class Source( - var value: String, +public enum class Source( + public var value: String, ) { DEFAULT("default"), OUTDOOR("outdoor"), 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..c5f1c0614 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/SupportMapFragment.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 + +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 fun SupportMapFragment.awaitMap(): GoogleMap = + suspendCancellableCoroutine { continuation -> + getMapAsync { + 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 new file mode 100644 index 000000000..d7e19936c --- /dev/null +++ b/library/src/main/java/com/google/maps/android/SupportStreetViewPanoramaFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.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 + +/** + * 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 fun SupportStreetViewPanoramaFragment.awaitStreetViewPanorama(): StreetViewPanorama = + suspendCancellableCoroutine { continuation -> + getStreetViewPanoramaAsync { + if (continuation.isActive) { + 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..e38f2d0f7 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 @@ -27,7 +27,7 @@ import kotlin.collections.Collection as KotlinCollection * All circle operations (adds and removes) should occur via its collection class. That is, don't * add a circle via a collection, then remove it via Circle.remove() */ -open class CircleManager(map: GoogleMap) : +public open class CircleManager(map: GoogleMap) : MapObjectManager(map), GoogleMap.OnCircleClickListener { @@ -35,36 +35,36 @@ open class CircleManager(map: GoogleMap) : mMap.setOnCircleClickListener(this) } - override fun newCollection(): Collection = Collection() + public override fun newCollection(): Collection = Collection() - override fun removeObjectFromMap(circle: Circle) { + public override fun removeObjectFromMap(circle: Circle) { circle.remove() } - override fun setVisible(mapObject: Circle, visible: Boolean) { + public override fun setVisible(mapObject: Circle, visible: Boolean) { mapObject.isVisible = visible } - override fun onCircleClick(circle: Circle) { + public override fun onCircleClick(circle: Circle) { mAllObjects[circle]?.mCircleClickListener?.onCircleClick(circle) } /** A collection of [Circle]s on the map with its own set of listeners. */ - open inner class Collection : MapObjectManager.Collection() { + public open inner class Collection : MapObjectManager.Collection() { internal var mCircleClickListener: GoogleMap.OnCircleClickListener? = null - open fun addCircle(opts: CircleOptions): Circle = + public open fun addCircle(opts: CircleOptions): Circle = checkAndAdd(mMap.addCircle(opts), "Circle") - open fun addAll(opts: KotlinCollection) = + public open fun addAll(opts: KotlinCollection): Unit = addAll(opts, ::addCircle) - open fun addAll(opts: KotlinCollection, defaultVisible: Boolean) = + public open fun addAll(opts: KotlinCollection, defaultVisible: Boolean): Unit = addAll(opts, defaultVisible, ::addCircle) - open fun getCircles(): KotlinCollection = getObjects() + public open fun getCircles(): KotlinCollection = getObjects() - open fun setOnCircleClickListener(circleClickListener: GoogleMap.OnCircleClickListener?) { + public open fun setOnCircleClickListener(circleClickListener: GoogleMap.OnCircleClickListener?) { mCircleClickListener = circleClickListener } } 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 7dd9d07d1..c9be7b9dc 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 @@ -27,7 +27,7 @@ import kotlin.collections.Collection as KotlinCollection * All ground overlay operations (adds and removes) should occur via its collection class. That * is, don't add a ground overlay via a collection, then remove it via GroundOverlay.remove() */ -open class GroundOverlayManager(map: GoogleMap) : +public open class GroundOverlayManager(map: GoogleMap) : MapObjectManager(map), GoogleMap.OnGroundOverlayClickListener { @@ -35,37 +35,37 @@ open class GroundOverlayManager(map: GoogleMap) : mMap.setOnGroundOverlayClickListener(this) } - override fun newCollection(): Collection = Collection() + public override fun newCollection(): Collection = Collection() - override fun removeObjectFromMap(groundOverlay: GroundOverlay) { + public override fun removeObjectFromMap(groundOverlay: GroundOverlay) { groundOverlay.remove() } - override fun setVisible(mapObject: GroundOverlay, visible: Boolean) { + public override fun setVisible(mapObject: GroundOverlay, visible: Boolean) { mapObject.isVisible = visible } - override fun onGroundOverlayClick(groundOverlay: GroundOverlay) { + public override fun onGroundOverlayClick(groundOverlay: GroundOverlay) { mAllObjects[groundOverlay]?.mGroundOverlayClickListener?.onGroundOverlayClick(groundOverlay) } /** A collection of [GroundOverlay]s on the map with its own set of listeners. */ - open inner class Collection : + public open inner class Collection : MapObjectManager.Collection() { internal var mGroundOverlayClickListener: GoogleMap.OnGroundOverlayClickListener? = null - open fun addGroundOverlay(opts: GroundOverlayOptions): GroundOverlay = + public open fun addGroundOverlay(opts: GroundOverlayOptions): GroundOverlay = checkAndAdd(mMap.addGroundOverlay(opts), "GroundOverlay") - open fun addAll(opts: KotlinCollection) = + public open fun addAll(opts: KotlinCollection): Unit = addAll(opts, ::addGroundOverlay) - open fun addAll(opts: KotlinCollection, defaultVisible: Boolean) = + public open fun addAll(opts: KotlinCollection, defaultVisible: Boolean): Unit = addAll(opts, defaultVisible, ::addGroundOverlay) - open fun getGroundOverlays(): KotlinCollection = getObjects() + public open fun getGroundOverlays(): KotlinCollection = getObjects() - open fun setOnGroundOverlayClickListener( + public open fun setOnGroundOverlayClickListener( groundOverlayClickListener: GoogleMap.OnGroundOverlayClickListener?, ) { mGroundOverlayClickListener = groundOverlayClickListener 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/MapObjectManager.kt b/library/src/main/java/com/google/maps/android/collections/MapObjectManager.kt index 5c6b7116a..c64f5e8d5 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 @@ -29,7 +29,7 @@ import kotlin.collections.Collection as KotlinCollection * All object operations (adds and removes) should occur via its collection class. That is, don't * add an object via a collection, then remove it via Object.remove() */ -abstract class MapObjectManager.Collection>( +public abstract class MapObjectManager.Collection>( @JvmField protected val mMap: GoogleMap, ) { @@ -39,21 +39,26 @@ 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() + } } } internal abstract fun setListenersOnUiThread() - abstract fun newCollection(): C + public abstract fun newCollection(): C /** * Create a new named collection, which can later be looked up by [getCollection] * * @param id a unique id for this collection. */ - open fun newCollection(id: String): C { + public open fun newCollection(id: String): C { require(mNamedCollections[id] == null) { "collection id is not unique: $id" } val collection = newCollection() mNamedCollections[id] = collection @@ -65,7 +70,7 @@ abstract class MapObjectManager.Collection>( * * @param id the unique id for this collection. */ - open fun getCollection(id: String): C? = mNamedCollections[id] + public open fun getCollection(id: String): C? = mNamedCollections[id] /** * Removes an object from its collection. @@ -73,14 +78,14 @@ abstract class MapObjectManager.Collection>( * @param mapObject the object to remove. * @return true if the object was removed. */ - open fun remove(mapObject: O?): Boolean = + public open fun remove(mapObject: O?): Boolean = mapObject != null && mAllObjects[mapObject]?.remove(mapObject) == true protected abstract fun removeObjectFromMap(mapObject: O) protected open fun setVisible(mapObject: O, visible: Boolean) {} - open inner class Collection { + public open inner class Collection { private val mObjects: MutableSet = mutableSetOf() // Safe unchecked cast: this inner collection is an instance of subclass C. @@ -93,13 +98,13 @@ abstract class MapObjectManager.Collection>( protected open fun checkAndAdd(mapObject: O?, typeName: String): O = checkNotNull(mapObject) { "Failed to add $typeName to GoogleMap" }.also { add(it) } - open fun showAll() { + public open fun showAll() { for (mapObject in mObjects) { setVisible(mapObject, true) } } - open fun hideAll() { + public open fun hideAll() { for (mapObject in mObjects) { setVisible(mapObject, false) } @@ -118,7 +123,7 @@ abstract class MapObjectManager.Collection>( } } - open fun remove(mapObject: O?): Boolean { + public open fun remove(mapObject: O?): Boolean { if (mapObject == null) return false if (mObjects.remove(mapObject)) { mAllObjects.remove(mapObject) @@ -128,7 +133,7 @@ abstract class MapObjectManager.Collection>( return false } - open fun clear() { + public open fun clear() { for (mapObject in mObjects) { removeObjectFromMap(mapObject) mAllObjects.remove(mapObject) 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..7f449c882 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 @@ -29,7 +29,7 @@ import kotlin.collections.Collection as KotlinCollection * All marker operations (adds and removes) should occur via its collection class. That is, don't * add a marker via a collection, then remove it via Marker.remove() */ -open class MarkerManager(map: GoogleMap) : +public open class MarkerManager(map: GoogleMap) : MapObjectManager(map), GoogleMap.OnInfoWindowClickListener, GoogleMap.OnMarkerClickListener, @@ -45,84 +45,84 @@ open class MarkerManager(map: GoogleMap) : mMap.setInfoWindowAdapter(this) } - override fun newCollection(): Collection = Collection() + public override fun newCollection(): Collection = Collection() - override fun getInfoWindow(marker: Marker): View? = + public override fun getInfoWindow(marker: Marker): View? = mAllObjects[marker]?.mInfoWindowAdapter?.getInfoWindow(marker) - override fun getInfoContents(marker: Marker): View? = + public override fun getInfoContents(marker: Marker): View? = mAllObjects[marker]?.mInfoWindowAdapter?.getInfoContents(marker) - override fun onInfoWindowClick(marker: Marker) { + public override fun onInfoWindowClick(marker: Marker) { mAllObjects[marker]?.mInfoWindowClickListener?.onInfoWindowClick(marker) } - override fun onInfoWindowLongClick(marker: Marker) { + public override fun onInfoWindowLongClick(marker: Marker) { mAllObjects[marker]?.mInfoWindowLongClickListener?.onInfoWindowLongClick(marker) } - override fun onMarkerClick(marker: Marker): Boolean = + public override fun onMarkerClick(marker: Marker): Boolean = mAllObjects[marker]?.mMarkerClickListener?.onMarkerClick(marker) ?: false - override fun onMarkerDragStart(marker: Marker) { + public override fun onMarkerDragStart(marker: Marker) { mAllObjects[marker]?.mMarkerDragListener?.onMarkerDragStart(marker) } - override fun onMarkerDrag(marker: Marker) { + public override fun onMarkerDrag(marker: Marker) { mAllObjects[marker]?.mMarkerDragListener?.onMarkerDrag(marker) } - override fun onMarkerDragEnd(marker: Marker) { + public override fun onMarkerDragEnd(marker: Marker) { mAllObjects[marker]?.mMarkerDragListener?.onMarkerDragEnd(marker) } - override fun removeObjectFromMap(marker: Marker) { + public override fun removeObjectFromMap(marker: Marker) { marker.remove() } - override fun setVisible(mapObject: Marker, visible: Boolean) { + public override fun setVisible(mapObject: Marker, visible: Boolean) { mapObject.isVisible = visible } /** A collection of [Marker]s on the map with its own set of listeners. */ - open inner class Collection : MapObjectManager.Collection() { + public open inner class Collection : MapObjectManager.Collection() { internal var mInfoWindowClickListener: GoogleMap.OnInfoWindowClickListener? = null internal var mInfoWindowLongClickListener: GoogleMap.OnInfoWindowLongClickListener? = null internal var mMarkerClickListener: GoogleMap.OnMarkerClickListener? = null internal var mMarkerDragListener: GoogleMap.OnMarkerDragListener? = null internal var mInfoWindowAdapter: GoogleMap.InfoWindowAdapter? = null - open fun addMarker(opts: MarkerOptions): Marker = + public open fun addMarker(opts: MarkerOptions): Marker = checkAndAdd(mMap.addMarker(opts), "Marker") - open fun addMarker(opts: AdvancedMarkerOptions): Marker = + public open fun addMarker(opts: AdvancedMarkerOptions): Marker = checkAndAdd(mMap.addMarker(opts), "AdvancedMarker") - open fun addAll(opts: KotlinCollection) = + public open fun addAll(opts: KotlinCollection): Unit = addAll(opts, ::addMarker) - open fun addAll(opts: KotlinCollection, defaultVisible: Boolean) = + public open fun addAll(opts: KotlinCollection, defaultVisible: Boolean): Unit = addAll(opts, defaultVisible, ::addMarker) - open fun getMarkers(): KotlinCollection = getObjects() + public open fun getMarkers(): KotlinCollection = getObjects() - open fun setOnInfoWindowClickListener(infoWindowClickListener: GoogleMap.OnInfoWindowClickListener?) { + public open fun setOnInfoWindowClickListener(infoWindowClickListener: GoogleMap.OnInfoWindowClickListener?) { mInfoWindowClickListener = infoWindowClickListener } - open fun setOnInfoWindowLongClickListener(infoWindowLongClickListener: GoogleMap.OnInfoWindowLongClickListener?) { + public open fun setOnInfoWindowLongClickListener(infoWindowLongClickListener: GoogleMap.OnInfoWindowLongClickListener?) { mInfoWindowLongClickListener = infoWindowLongClickListener } - open fun setOnMarkerClickListener(markerClickListener: GoogleMap.OnMarkerClickListener?) { + public open fun setOnMarkerClickListener(markerClickListener: GoogleMap.OnMarkerClickListener?) { mMarkerClickListener = markerClickListener } - open fun setOnMarkerDragListener(markerDragListener: GoogleMap.OnMarkerDragListener?) { + public open fun setOnMarkerDragListener(markerDragListener: GoogleMap.OnMarkerDragListener?) { mMarkerDragListener = markerDragListener } - open fun setInfoWindowAdapter(infoWindowAdapter: GoogleMap.InfoWindowAdapter?) { + public open fun setInfoWindowAdapter(infoWindowAdapter: GoogleMap.InfoWindowAdapter?) { mInfoWindowAdapter = infoWindowAdapter } } 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 98eef696a..5b0b0ab71 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 @@ -27,7 +27,7 @@ import kotlin.collections.Collection as KotlinCollection * All polygon operations (adds and removes) should occur via its collection class. That is, * don't add a polygon via a collection, then remove it via Polygon.remove() */ -open class PolygonManager(map: GoogleMap) : +public open class PolygonManager(map: GoogleMap) : MapObjectManager(map), GoogleMap.OnPolygonClickListener { @@ -35,36 +35,36 @@ open class PolygonManager(map: GoogleMap) : mMap.setOnPolygonClickListener(this) } - override fun newCollection(): Collection = Collection() + public override fun newCollection(): Collection = Collection() - override fun removeObjectFromMap(polygon: Polygon) { + public override fun removeObjectFromMap(polygon: Polygon) { polygon.remove() } - override fun setVisible(mapObject: Polygon, visible: Boolean) { + public override fun setVisible(mapObject: Polygon, visible: Boolean) { mapObject.isVisible = visible } - override fun onPolygonClick(polygon: Polygon) { + public override fun onPolygonClick(polygon: Polygon) { mAllObjects[polygon]?.mPolygonClickListener?.onPolygonClick(polygon) } /** A collection of [Polygon]s on the map with its own set of listeners. */ - open inner class Collection : MapObjectManager.Collection() { + public open inner class Collection : MapObjectManager.Collection() { internal var mPolygonClickListener: GoogleMap.OnPolygonClickListener? = null - open fun addPolygon(opts: PolygonOptions): Polygon = + public open fun addPolygon(opts: PolygonOptions): Polygon = checkAndAdd(mMap.addPolygon(opts), "Polygon") - open fun addAll(opts: KotlinCollection) = + public open fun addAll(opts: KotlinCollection): Unit = addAll(opts, ::addPolygon) - open fun addAll(opts: KotlinCollection, defaultVisible: Boolean) = + public open fun addAll(opts: KotlinCollection, defaultVisible: Boolean): Unit = addAll(opts, defaultVisible, ::addPolygon) - open fun getPolygons(): KotlinCollection = getObjects() + public open fun getPolygons(): KotlinCollection = getObjects() - open fun setOnPolygonClickListener(polygonClickListener: GoogleMap.OnPolygonClickListener?) { + public open fun setOnPolygonClickListener(polygonClickListener: GoogleMap.OnPolygonClickListener?) { mPolygonClickListener = polygonClickListener } } 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 61de4b7ef..1d77f91d3 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 @@ -27,7 +27,7 @@ import kotlin.collections.Collection as KotlinCollection * All polyline operations (adds and removes) should occur via its collection class. That is, * don't add a polyline via a collection, then remove it via Polyline.remove() */ -open class PolylineManager(map: GoogleMap) : +public open class PolylineManager(map: GoogleMap) : MapObjectManager(map), GoogleMap.OnPolylineClickListener { @@ -35,36 +35,36 @@ open class PolylineManager(map: GoogleMap) : mMap.setOnPolylineClickListener(this) } - override fun newCollection(): Collection = Collection() + public override fun newCollection(): Collection = Collection() - override fun removeObjectFromMap(polyline: Polyline) { + public override fun removeObjectFromMap(polyline: Polyline) { polyline.remove() } - override fun setVisible(mapObject: Polyline, visible: Boolean) { + public override fun setVisible(mapObject: Polyline, visible: Boolean) { mapObject.isVisible = visible } - override fun onPolylineClick(polyline: Polyline) { + public override fun onPolylineClick(polyline: Polyline) { mAllObjects[polyline]?.mPolylineClickListener?.onPolylineClick(polyline) } /** A collection of [Polyline]s on the map with its own set of listeners. */ - open inner class Collection : MapObjectManager.Collection() { + public open inner class Collection : MapObjectManager.Collection() { internal var mPolylineClickListener: GoogleMap.OnPolylineClickListener? = null - open fun addPolyline(opts: PolylineOptions): Polyline = + public open fun addPolyline(opts: PolylineOptions): Polyline = checkAndAdd(mMap.addPolyline(opts), "Polyline") - open fun addAll(opts: KotlinCollection) = + public open fun addAll(opts: KotlinCollection): Unit = addAll(opts, ::addPolyline) - open fun addAll(opts: KotlinCollection, defaultVisible: Boolean) = + public open fun addAll(opts: KotlinCollection, defaultVisible: Boolean): Unit = addAll(opts, defaultVisible, ::addPolyline) - open fun getPolylines(): KotlinCollection = getObjects() + public open fun getPolylines(): KotlinCollection = getObjects() - open fun setOnPolylineClickListener( + public open fun setOnPolylineClickListener( polylineClickListener: GoogleMap.OnPolylineClickListener?, ) { mPolylineClickListener = polylineClickListener 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 new file mode 100644 index 000000000..dcb55f5f9 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/GoogleMap.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. + */ +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.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 +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 = CanonicalMoveStartedReason + +@Deprecated("Moved to com.google.maps.android.CameraEvent", ReplaceWith("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 = CanonicalCameraIdleEvent + +@Deprecated("Moved to com.google.maps.android.CameraMoveCanceledEvent", ReplaceWith("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 = CanonicalCameraMoveEvent + +@Deprecated("Moved to com.google.maps.android.CameraMoveStartedEvent", ReplaceWith("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 = CanonicalOnMarkerDragEvent + +@Deprecated("Moved to com.google.maps.android.MarkerDragEvent", ReplaceWith("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 = CanonicalMarkerDragEndEvent + +@Deprecated("Moved to com.google.maps.android.MarkerDragStartEvent", ReplaceWith("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 = CanonicalIndoorChangeEvent + +@Deprecated("Moved to com.google.maps.android.IndoorBuildingFocusedEvent", ReplaceWith("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 = 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 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() + +@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 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() + +@Suppress("DEPRECATION") +@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() + +@Suppress("DEPRECATION") +@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..41e8a200a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapFragment.kt @@ -0,0 +1,28 @@ +/* + * 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 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..128df239d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapView.kt @@ -0,0 +1,28 @@ +/* + * 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 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/MapsInitializer.kt b/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.kt new file mode 100644 index 000000000..19e18030d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/MapsInitializer.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 + +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 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 new file mode 100644 index 000000000..3dccb8729 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaFragment.kt @@ -0,0 +1,28 @@ +/* + * 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 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..dfd5b9c6d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/StreetViewPanoramaView.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.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 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..8621a0683 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/SupportMapFragment.kt @@ -0,0 +1,28 @@ +/* + * 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 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..07cc270c3 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/SupportStreetViewPanoramaFragment.kt @@ -0,0 +1,28 @@ +/* + * 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 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..2b3ee122e --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/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. + */ +@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 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 = 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 new file mode 100644 index 000000000..9a0eb5f51 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/CircleOptions.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..9e285218d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/GroundOverlayOptions.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..81ea9d377 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/MarkerOptions.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..de0bdca90 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/PolygonOptions.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..b86868529 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/PolylineOptions.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..6112674a9 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaCamera.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..ea9582e48 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientation.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..9c2e27dcc --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/model/TileOverlayOptions.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. + */ +@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 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 = 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 new file mode 100644 index 000000000..cb7e7174f --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/LatLng.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.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 operator fun LatLng.component1(): Double = this.canonicalComponent1() + +@Deprecated("Moved to com.google.maps.android.component2", ReplaceWith("component2()", "com.google.maps.android.component2")) +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 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 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 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 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 fun String.toLatLngList(): List = this.canonicalToLatLngList() + +@Deprecated("Moved to com.google.maps.android.latLngListEncode", ReplaceWith("latLngListEncode()", "com.google.maps.android.latLngListEncode")) +public fun List.latLngListEncode(): String = this.canonicalLatLngListEncode() + +@Deprecated("Moved to com.google.maps.android.isClosedPolygon", ReplaceWith("isClosedPolygon()", "com.google.maps.android.isClosedPolygon")) +public fun List.isClosedPolygon(): Boolean = this.canonicalIsClosedPolygon() + +@Deprecated("Moved to com.google.maps.android.sphericalPathLength", ReplaceWith("sphericalPathLength()", "com.google.maps.android.sphericalPathLength")) +public fun List.sphericalPathLength(): Double = this.canonicalSphericalPathLength() + +@Deprecated("Moved to com.google.maps.android.sphericalPolygonArea", ReplaceWith("sphericalPolygonArea()", "com.google.maps.android.sphericalPolygonArea")) +public fun List.sphericalPolygonArea(): Double = this.canonicalSphericalPolygonArea() + +@Deprecated("Moved to com.google.maps.android.sphericalPolygonSignedArea", ReplaceWith("sphericalPolygonSignedArea()", "com.google.maps.android.sphericalPolygonSignedArea")) +public fun List.sphericalPolygonSignedArea(): Double = this.canonicalSphericalPolygonSignedArea() + +@Deprecated("Moved to com.google.maps.android.sphericalHeading", ReplaceWith("sphericalHeading(toLatLng)", "com.google.maps.android.sphericalHeading")) +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 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 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 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 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..48d71680d --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/Polygon.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.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 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 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 val Polygon.area: Double get() = this.canonicalArea + +@Deprecated("Moved to com.google.maps.android.signedArea", ReplaceWith("signedArea", "com.google.maps.android.signedArea")) +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 new file mode 100644 index 000000000..0f0850670 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/Polyline.kt @@ -0,0 +1,28 @@ +/* + * 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 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 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 new file mode 100644 index 000000000..d45562e7a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/CircleManager.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.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 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 + +@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.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 new file mode 100644 index 000000000..61a7267da --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/GroundOverlayManager.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. + */ +@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 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 + +@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.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 new file mode 100644 index 000000000..f24a8a989 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/MarkerManager.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. + */ +@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 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 + +@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.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() + +@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..e81af8b6a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolygonManager.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.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 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 + +@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.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 new file mode 100644 index 000000000..944da71a0 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/collection/PolylineManager.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.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 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 + +@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.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/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..ad7b575c0 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/ktx/utils/location/LocationManager.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.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, looper)", "com.google.maps.android.location.coarseLocationEvents")) +public fun LocationManager.coarseLocationEvents( + 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, looper)", "com.google.maps.android.location.fineLocationEvents")) +public fun LocationManager.fineLocationEvents( + 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/FusedLocationProvider.kt b/library/src/main/java/com/google/maps/android/location/FusedLocationProvider.kt new file mode 100644 index 000000000..0dab48af6 --- /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 = 2_000L, + 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..ac8b8b8a2 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/location/LocationManager.kt @@ -0,0 +1,141 @@ +/* + * 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 android.os.Looper +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]. + * + * 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 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 + * 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 = 1_000L, + minDistanceM: Float = 1f, + 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) + } + + @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() + } + } + + requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + minTimeMs, + minDistanceM, + listener, + looper + ) + + 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. 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 + * 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 = 1_000L, + minDistanceM: Float = 1f, + 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) + } + + @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() + } + } + + requestLocationUpdates( + LocationManager.GPS_PROVIDER, + minTimeMs, + minDistanceM, + listener, + looper + ) + + 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/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/GoogleMapTest.kt b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt new file mode 100644 index 000000000..2c5762a6c --- /dev/null +++ b/library/src/test/java/com/google/maps/android/GoogleMapTest.kt @@ -0,0 +1,606 @@ +/* + * 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 com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.GoogleMap +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.async +import kotlinx.coroutines.flow.first +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.ArgumentMatchers.any +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +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 `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`, + * 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(deferred.await()).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 + + @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 + + @Before + public fun setUp() { + 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) + } + + // --------------------------------------------------------------------------------------------- + // 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 `deferred.await()` completes when `onCameraIdle()` is called. + */ + @Test + public fun testCameraIdleEvents(): Unit = runTest { + val deferred = async { + googleMap.cameraIdleEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) + cameraIdleListener.value.onCameraIdle() + 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 `deferred.await()` returns `Unit` when `onCameraMove()` is invoked. + */ + @Test + public fun testCameraMoveEvents(): Unit = runTest { + val deferred = async { + googleMap.cameraMoveEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnCameraMoveListener(cameraMoveListener.capture()) + cameraMoveListener.value.onCameraMove() + assertThat(deferred.await()).isEqualTo(Unit) + } + + /** + * **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 deferred = async { + googleMap.cameraMoveStartedEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnCameraMoveStartedListener(cameraMoveStartedListener.capture()) + cameraMoveStartedListener.value.onCameraMoveStarted(GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) + 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 Unit event upon cancellation. + */ + @Test + public fun testCameraMoveCanceledEvents(): Unit = runTest { + val deferred = async { + googleMap.cameraMoveCanceledEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnCameraMoveCanceledListener(cameraMoveCanceledListener.capture()) + cameraMoveCanceledListener.value.onCameraMoveCanceled() + assertThat(deferred.await()).isEqualTo(Unit) + } + + + /** + * **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 deferred = async { + googleMap.mapClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnMapClickListener(mapClickListener.capture()) + mapClickListener.value.onMapClick(target) + assertThat(deferred.await()).isEqualTo(target) + } + + /** + * **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 deferred = async { + googleMap.mapLongClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnMapLongClickListener(mapLongClickListener.capture()) + mapLongClickListener.value.onMapLongClick(target) + assertThat(deferred.await()).isEqualTo(target) + } + + /** + * **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 deferred = async { + googleMap.markerClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnMarkerClickListener(markerClickListener.capture()) + markerClickListener.value.onMarkerClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + /** + * **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 deferred = async { + googleMap.markerDragEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnMarkerDragListener(markerDragListener.capture()) + markerDragListener.value.onMarkerDragStart(marker) + assertThat(deferred.await().marker).isEqualTo(marker) + } + + /** + * **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 deferred = async { + googleMap.infoWindowClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnInfoWindowClickListener(infoWindowClickListener.capture()) + infoWindowClickListener.value.onInfoWindowClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + /** + * **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 deferred = async { + googleMap.infoWindowCloseEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnInfoWindowCloseListener(infoWindowCloseListener.capture()) + infoWindowCloseListener.value.onInfoWindowClose(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + /** + * **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 deferred = async { + googleMap.infoWindowLongClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) + infoWindowLongClickListener.value.onInfoWindowLongClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + /** + * **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 deferred = async { + googleMap.polygonClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnPolygonClickListener(polygonClickListener.capture()) + polygonClickListener.value.onPolygonClick(polygon) + assertThat(deferred.await()).isEqualTo(polygon) + } + + /** + * **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 deferred = async { + googleMap.polylineClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnPolylineClickListener(polylineClickListener.capture()) + polylineClickListener.value.onPolylineClick(polyline) + assertThat(deferred.await()).isEqualTo(polyline) + } + + /** + * **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 deferred = async { + googleMap.circleClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnCircleClickListener(circleClickListener.capture()) + circleClickListener.value.onCircleClick(circle) + assertThat(deferred.await()).isEqualTo(circle) + } + + /** + * **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 deferred = async { + googleMap.groundOverlayClicks().first() + } + advanceUntilIdle() + verify(googleMap).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) + groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) + assertThat(deferred.await()).isEqualTo(groundOverlay) + } + + /** + * **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 deferred = async { + googleMap.poiClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnPoiClickListener(poiClickListener.capture()) + poiClickListener.value.onPoiClick(poi) + assertThat(deferred.await()).isEqualTo(poi) + } + + /** + * **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 deferred = async { + googleMap.myLocationClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnMyLocationClickListener(myLocationClickListener.capture()) + myLocationClickListener.value.onMyLocationClick(location) + 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 received via `deferred.await()`. + */ + @Test + public fun testMyLocationButtonClickEvents(): Unit = runTest { + val deferred = async { + googleMap.myLocationButtonClickEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnMyLocationButtonClickListener(myLocationButtonClickListener.capture()) + myLocationButtonClickListener.value.onMyLocationButtonClick() + assertThat(deferred.await()).isEqualTo(Unit) + } + + /** + * **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 deferred = async { + googleMap.indoorStateChangeEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnIndoorStateChangeListener(indoorStateChangeListener.capture()) + indoorStateChangeListener.value.onIndoorBuildingFocused() + assertThat(deferred.await()).isNotNull() + } + + // --------------------------------------------------------------------------------------------- + // 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 deferred = async { + googleMap.awaitMapLoad() + } + advanceUntilIdle() + verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) + loadedCallback.value.onMapLoaded() + assertThat(deferred.await()).isEqualTo(Unit) + } + + /** + * **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 { + val deferredDefault = async { + googleMap.awaitAnimateCamera(cameraUpdate) + } + advanceUntilIdle() + 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() + verify(googleMap).animateCamera(any(CameraUpdate::class.java), Mockito.eq(500), cancelableCallback.capture()) + cancelableCallback.value.onFinish() + assertThat(deferredWithDuration.await()).isEqualTo(Unit) + } + + + /** + * **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) + } + val deferred = async { + googleMap.awaitSnapshot(bitmap) + } + assertThat(deferred.await()).isEqualTo(bitmap) + } + + // --------------------------------------------------------------------------------------------- + // 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..5fc97db3c --- /dev/null +++ b/library/src/test/java/com/google/maps/android/MapFragmentExtensionsTest.kt @@ -0,0 +1,443 @@ +/* + * 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. + */ + @Suppress("DEPRECATION") + @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`. + */ + @Suppress("DEPRECATION") + @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`. + */ + @Suppress("DEPRECATION") + @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`. + */ + @Suppress("DEPRECATION") + @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`. + */ + @Suppress("DEPRECATION") + @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`. + */ + @Suppress("DEPRECATION") + @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/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/PolygonTest.kt b/library/src/test/java/com/google/maps/android/PolygonTest.kt new file mode 100644 index 000000000..312685436 --- /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 org.mockito.kotlin.doReturn +import org.mockito.kotlin.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..26304ab07 --- /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 org.mockito.kotlin.doReturn +import org.mockito.kotlin.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..9118b9d33 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/collections/CollectionManagersTest.kt @@ -0,0 +1,247 @@ +/* + * 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.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 +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.async +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.`when` +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 + `when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> + activeMarkerClickListener = invocation.arguments[0] as? GoogleMap.OnMarkerClickListener + null + } + } + + @Test + public fun testMarkerCollectionClickEvents(): Unit = runTest { + val deferred = async { + markerCollection.clickEvents().first() + } + advanceUntilIdle() + // Trigger the event via our tracked active listener slot! + assertThat(activeMarkerClickListener).isNotNull() + activeMarkerClickListener?.onMarkerClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + @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 deferred = async { + markerCollection.infoWindowClickEvents().first() + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowClickListener(infoWindowClickListener.capture()) + infoWindowClickListener.value.onInfoWindowClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + @Test + public fun testMarkerCollectionInfoWindowLongClickEvents(): Unit = runTest { + val deferred = async { + markerCollection.infoWindowLongClickEvents().first() + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) + infoWindowLongClickListener.value.onInfoWindowLongClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + @Test + public fun testPolylineCollectionClickEvents(): Unit = runTest { + val deferred = async { + polylineCollection.clickEvents().first() + } + advanceUntilIdle() + verify(polylineCollection).setOnPolylineClickListener(polylineClickListener.capture()) + polylineClickListener.value.onPolylineClick(polyline) + assertThat(deferred.await()).isEqualTo(polyline) + } + + @Test + public fun testPolygonCollectionClickEvents(): Unit = runTest { + val deferred = async { + polygonCollection.clickEvents().first() + } + advanceUntilIdle() + verify(polygonCollection).setOnPolygonClickListener(polygonClickListener.capture()) + polygonClickListener.value.onPolygonClick(polygon) + assertThat(deferred.await()).isEqualTo(polygon) + } + + @Test + public fun testCircleCollectionClickEvents(): Unit = runTest { + val deferred = async { + circleCollection.clickEvents().first() + } + advanceUntilIdle() + verify(circleCollection).setOnCircleClickListener(circleClickListener.capture()) + circleClickListener.value.onCircleClick(circle) + assertThat(deferred.await()).isEqualTo(circle) + } + + @Test + public fun testGroundOverlayCollectionClickEvents(): Unit = runTest { + val deferred = async { + groundOverlayCollection.clickEvents().first() + } + advanceUntilIdle() + verify(groundOverlayCollection).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) + groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) + assertThat(deferred.await()).isEqualTo(groundOverlay) + } +} 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/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..dd8b42f29 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/GoogleMapTest.kt @@ -0,0 +1,98 @@ +/* + * 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("DEPRECATION") + +package com.google.maps.android.ktx + +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.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 +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner + +@ExperimentalCoroutinesApi +@RunWith(MockitoJUnitRunner::class) +public class GoogleMapTest { + + @Mock + private lateinit var googleMap: GoogleMap + + @Mock + private lateinit var cameraUpdate: CameraUpdate + + @Captor + private lateinit var cameraIdleListener: ArgumentCaptor + + @Captor + private lateinit var loadedCallback: ArgumentCaptor + + @Captor + private lateinit var cancelableCallback: ArgumentCaptor + + @Test + public fun testCameraIdleEvents(): Unit = runTest { + val deferred = async { + googleMap.cameraIdleEvents().first() + } + advanceUntilIdle() + verify(googleMap).setOnCameraIdleListener(cameraIdleListener.capture()) + cameraIdleListener.value.onCameraIdle() + assertThat(deferred.await()).isEqualTo(Unit) + } + + @Test + public fun testAwaitMapLoad(): Unit = runTest { + val deferred = async { + googleMap.awaitMapLoad() + } + advanceUntilIdle() + verify(googleMap).setOnMapLoadedCallback(loadedCallback.capture()) + loadedCallback.value.onMapLoaded() + assertThat(deferred.await()).isEqualTo(Unit) + } + + @Test + public fun testAwaitAnimateCamera(): Unit = runTest { + val deferredDefault = async { + googleMap.awaitAnimateCamera(cameraUpdate) + } + advanceUntilIdle() + 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() + 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/MapsInitializerTest.kt b/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt new file mode 100644 index 000000000..1f15efa05 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/MapsInitializerTest.kt @@ -0,0 +1,118 @@ +/* + * 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("DEPRECATION") + +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) + } +} 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..a5f3016cc --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/CameraPositionTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("DEPRECATION") + +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..4aac4f2cd --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/CircleOptionsTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("DEPRECATION") + +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..477f8d60e --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/GroundOverlayOptionsTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("DEPRECATION") + +package com.google.maps.android.ktx.model + +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.common.truth.Truth.assertThat +import org.mockito.kotlin.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..23cdef1e4 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/MarkerOptionsTest.kt @@ -0,0 +1,46 @@ +/* + * 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("DEPRECATION") + +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..85c884197 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/PolygonOptionsTest.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. + * + */ +@file:Suppress("DEPRECATION") + +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..3d9f986c2 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/PolylineOptionsTest.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("DEPRECATION") + +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..d900a8ef8 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaCameraTest.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("DEPRECATION") + +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..56a9443d0 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/StreetViewPanoramaOrientationTest.kt @@ -0,0 +1,35 @@ +/* + * 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("DEPRECATION") + +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..c1d3c24c4 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/model/TileOverlayOptionsTest.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. + * + */ +@file:Suppress("DEPRECATION") + +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..136748560 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/LatLngTest.kt @@ -0,0 +1,190 @@ +/* + * 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("DEPRECATION") + +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..f3f0df6e5 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolygonTest.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. + * + */ +@file:Suppress("DEPRECATION") + +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 org.mockito.kotlin.doReturn +import org.mockito.kotlin.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..4b32a2b75 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/PolylineTest.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("DEPRECATION") + +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 org.mockito.kotlin.doReturn +import org.mockito.kotlin.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..ebca42b2c --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/collection/CollectionManagersTest.kt @@ -0,0 +1,248 @@ +/* + * 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("DEPRECATION") + +package com.google.maps.android.ktx.utils.collection + +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 +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.async +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.`when` +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 + `when`(markerCollection.setOnMarkerClickListener(any())).thenAnswer { invocation -> + activeMarkerClickListener = invocation.arguments[0] as? GoogleMap.OnMarkerClickListener + null + } + } + + @Test + public fun testMarkerCollectionClickEvents(): Unit = runTest { + val deferred = async { + markerCollection.clickEvents().first() + } + advanceUntilIdle() + // Trigger the event via our tracked active listener slot! + assertThat(activeMarkerClickListener).isNotNull() + activeMarkerClickListener?.onMarkerClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + @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 deferred = async { + markerCollection.infoWindowClickEvents().first() + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowClickListener(infoWindowClickListener.capture()) + infoWindowClickListener.value.onInfoWindowClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + @Test + public fun testMarkerCollectionInfoWindowLongClickEvents(): Unit = runTest { + val deferred = async { + markerCollection.infoWindowLongClickEvents().first() + } + advanceUntilIdle() + verify(markerCollection).setOnInfoWindowLongClickListener(infoWindowLongClickListener.capture()) + infoWindowLongClickListener.value.onInfoWindowLongClick(marker) + assertThat(deferred.await()).isEqualTo(marker) + } + + @Test + public fun testPolylineCollectionClickEvents(): Unit = runTest { + val deferred = async { + polylineCollection.clickEvents().first() + } + advanceUntilIdle() + verify(polylineCollection).setOnPolylineClickListener(polylineClickListener.capture()) + polylineClickListener.value.onPolylineClick(polyline) + assertThat(deferred.await()).isEqualTo(polyline) + } + + @Test + public fun testPolygonCollectionClickEvents(): Unit = runTest { + val deferred = async { + polygonCollection.clickEvents().first() + } + advanceUntilIdle() + verify(polygonCollection).setOnPolygonClickListener(polygonClickListener.capture()) + polygonClickListener.value.onPolygonClick(polygon) + assertThat(deferred.await()).isEqualTo(polygon) + } + + @Test + public fun testCircleCollectionClickEvents(): Unit = runTest { + val deferred = async { + circleCollection.clickEvents().first() + } + advanceUntilIdle() + verify(circleCollection).setOnCircleClickListener(circleClickListener.capture()) + circleClickListener.value.onCircleClick(circle) + assertThat(deferred.await()).isEqualTo(circle) + } + + @Test + public fun testGroundOverlayCollectionClickEvents(): Unit = runTest { + val deferred = async { + groundOverlayCollection.clickEvents().first() + } + advanceUntilIdle() + verify(groundOverlayCollection).setOnGroundOverlayClickListener(groundOverlayClickListener.capture()) + groundOverlayClickListener.value.onGroundOverlayClick(groundOverlay) + assertThat(deferred.await()).isEqualTo(groundOverlay) + } +} 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..91936a513 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/FusedLocationProviderTest.kt @@ -0,0 +1,117 @@ +/* + * 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("DEPRECATION") + +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..94b8375e6 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/ktx/utils/location/LocationManagerTest.kt @@ -0,0 +1,169 @@ +/* + * 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("DEPRECATION") + +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 android.os.Looper +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.first +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 +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 + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class LocationManagerTest { + + @Mock + private lateinit var locationManager: LocationManager + + @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 { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).first() + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + eq(1_000L), + eq(1f), + locationListenerCaptor.capture(), + eq(looper) + ) + + locationListenerCaptor.value.onLocationChanged(location) + assertThat(deferred.await()).isEqualTo(location) + + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + } + + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationProviderDisabled(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).toList() + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + anyLong(), + anyFloat(), + locationListenerCaptor.capture(), + any() + ) + + locationListenerCaptor.value.onProviderDisabled(LocationManager.NETWORK_PROVIDER) + advanceUntilIdle() + + assertThat(deferred.await()).isEmpty() + 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() + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.GPS_PROVIDER), + eq(2_000L), + eq(2f), + locationListenerCaptor.capture(), + eq(looper) + ) + + locationListenerCaptor.value.onLocationChanged(location) + assertThat(deferred.await()).isEqualTo(location) + + 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/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..f2dcd8ce2 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/location/LocationManagerTest.kt @@ -0,0 +1,169 @@ +/* + * 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 android.os.Looper +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.first +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 +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 + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(MockitoJUnitRunner::class) +public class LocationManagerTest { + + @Mock + private lateinit var locationManager: LocationManager + + @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 { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).first() + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + eq(1_000L), + eq(1f), + locationListenerCaptor.capture(), + eq(looper) + ) + + locationListenerCaptor.value.onLocationChanged(location) + assertThat(deferred.await()).isEqualTo(location) + + verify(locationManager).removeUpdates(eq(locationListenerCaptor.value)) + } + + @SuppressLint("MissingPermission") + @Test + public fun testCoarseLocationProviderDisabled(): Unit = runTest { + `when`(locationManager.allProviders).thenReturn(listOf(LocationManager.NETWORK_PROVIDER)) + + val deferred = async { + locationManager.coarseLocationEvents(1_000L, 1f, looper).toList() + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.NETWORK_PROVIDER), + anyLong(), + anyFloat(), + locationListenerCaptor.capture(), + any() + ) + + locationListenerCaptor.value.onLocationChanged(location) + locationListenerCaptor.value.onProviderDisabled(LocationManager.NETWORK_PROVIDER) + + // 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)) + } + + @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() + } + advanceUntilIdle() + + verify(locationManager).requestLocationUpdates( + eq(LocationManager.GPS_PROVIDER), + eq(2_000L), + eq(2f), + locationListenerCaptor.capture(), + eq(looper) + ) + + locationListenerCaptor.value.onLocationChanged(location) + assertThat(deferred.await()).isEqualTo(location) + + 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/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..57efe55b4 --- /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 org.mockito.kotlin.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..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 @@ -16,46 +16,75 @@ 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]. + * + * **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) - - verify { - MapsApiSettings.addInternalUsageAttributionId( - context, - AttributionId.VALUE, - ) + mapsApiSettingsMock.verify { + MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE) } } } 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..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,12 +17,16 @@ 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(): TestLintTask = + super.lint().allowMissingSdk() + fun testSetOnMarkerDragListener() { lint() .files( diff --git a/llm-integration-prompt.md b/llm-integration-prompt.md index 1c3227120..3b5685a8c 100644 --- a/llm-integration-prompt.md +++ b/llm-integration-prompt.md @@ -16,6 +16,7 @@ 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/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", diff --git a/ui/api/ui.api b/ui/api/ui.api new file mode 100644 index 000000000..a5407f524 --- /dev/null +++ b/ui/api/ui.api @@ -0,0 +1,86 @@ +public final class com/google/maps/android/ui/AnimationUtil { + public static final field INSTANCE Lcom/google/maps/android/ui/AnimationUtil; + public static final fun animateMarkerTo (Lcom/google/android/gms/maps/model/Marker;Lcom/google/android/gms/maps/model/LatLng;)V + public static final fun animateMarkerTo (Lcom/google/android/gms/maps/model/Marker;Lcom/google/android/gms/maps/model/LatLng;J)V + public static synthetic fun animateMarkerTo$default (Lcom/google/android/gms/maps/model/Marker;Lcom/google/android/gms/maps/model/LatLng;JILjava/lang/Object;)V +} + +public abstract interface class com/google/maps/android/ui/AnimationUtil$LatLngInterpolator { + public abstract fun interpolate (FLcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)Lcom/google/android/gms/maps/model/LatLng; +} + +public final class com/google/maps/android/ui/AnimationUtil$LatLngInterpolator$Linear : com/google/maps/android/ui/AnimationUtil$LatLngInterpolator { + public fun ()V + public fun interpolate (FLcom/google/android/gms/maps/model/LatLng;Lcom/google/android/gms/maps/model/LatLng;)Lcom/google/android/gms/maps/model/LatLng; +} + +public final class com/google/maps/android/ui/IconGenerator { + public static final field Companion Lcom/google/maps/android/ui/IconGenerator$Companion; + public static final field STYLE_BLUE I + public static final field STYLE_DEFAULT I + public static final field STYLE_GREEN I + public static final field STYLE_ORANGE I + public static final field STYLE_PURPLE I + public static final field STYLE_RED I + public static final field STYLE_WHITE I + public fun (Landroid/content/Context;)V + public final fun getAnchorU ()F + public final fun getAnchorV ()F + public final fun makeIcon ()Landroid/graphics/Bitmap; + public final fun makeIcon (Ljava/lang/CharSequence;)Landroid/graphics/Bitmap; + public final fun setBackground (Landroid/graphics/drawable/Drawable;)V + public final fun setColor (I)V + public final fun setContentPadding (IIII)V + public final fun setContentRotation (I)V + public final fun setContentView (Landroid/view/View;)V + public final fun setRotation (I)V + public final fun setStyle (I)V + public final fun setTextAppearance (I)V + public final fun setTextAppearance (Landroid/content/Context;I)V +} + +public final class com/google/maps/android/ui/IconGenerator$Companion { +} + +public final class com/google/maps/android/ui/R$drawable { + public static field amu_bubble_mask I + public static field amu_bubble_shadow I +} + +public final class com/google/maps/android/ui/R$id { + public static field amu_text I + public static field webview I + public static field window I +} + +public final class com/google/maps/android/ui/R$layout { + public static field amu_info_window I + public static field amu_text_bubble I + public static field amu_webview I +} + +public final class com/google/maps/android/ui/R$style { + public static field amu_Bubble_TextAppearance_Dark I + public static field amu_Bubble_TextAppearance_Light I + public static field amu_ClusterIcon_TextAppearance I +} + +public final class com/google/maps/android/ui/RotationLayout : android/widget/FrameLayout { + public fun (Landroid/content/Context;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V + public synthetic fun (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun dispatchDraw (Landroid/graphics/Canvas;)V + public fun onMeasure (II)V + public final fun setViewRotation (I)V +} + +public final class com/google/maps/android/ui/SquareTextView : android/widget/TextView { + public fun (Landroid/content/Context;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V + public synthetic fun (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun draw (Landroid/graphics/Canvas;)V + public fun onMeasure (II)V +} + diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 9c3cabd59..0ebd79a15 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -48,6 +48,7 @@ android { } kotlin { + explicitApi() compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } @@ -76,10 +77,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") { 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..c9315c77d 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 @@ -27,7 +27,7 @@ import kotlin.math.sign /** * Animation utilities for markers with Maps API. */ -object AnimationUtil { +public object AnimationUtil { /** * Animates a marker from its current position to the provided finalPosition. * @@ -37,11 +37,15 @@ object AnimationUtil { */ @JvmOverloads @JvmStatic - fun animateMarkerTo( + public fun animateMarkerTo( marker: Marker, 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) } @@ -69,15 +74,15 @@ object AnimationUtil { /** * For other LatLngInterpolator interpolators, see [link here](https://gist.github.com/broady/6314689) */ - interface LatLngInterpolator { - fun interpolate( + public interface LatLngInterpolator { + public fun interpolate( fraction: Float, a: LatLng, b: LatLng, ): LatLng - class Linear : LatLngInterpolator { - override fun interpolate( + public class Linear : LatLngInterpolator { + public override fun interpolate( fraction: Float, a: LatLng, b: LatLng, 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..015e9d5a3 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 @@ -37,7 +37,7 @@ import androidx.core.widget.TextViewCompat * * This class is not thread safe. */ -class IconGenerator(private val context: Context) { +public class IconGenerator(private val context: Context) { private val container: ViewGroup private val rotationLayout: RotationLayout private var textView: TextView? = null @@ -62,7 +62,7 @@ class IconGenerator(private val context: Context) { * * @param text the text content to display inside the icon. */ - fun makeIcon(text: CharSequence?): Bitmap { + public fun makeIcon(text: CharSequence?): Bitmap { if (textView != null) { textView!!.text = text } @@ -76,7 +76,7 @@ class IconGenerator(private val context: Context) { * applicable. */ @Suppress("UseKtx") - fun makeIcon(): Bitmap { + public fun makeIcon(): Bitmap { val measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) container.measure(measureSpec, measureSpec) var measuredWidth = container.measuredWidth @@ -110,7 +110,7 @@ class IconGenerator(private val context: Context) { * * If the view contains a [TextView] with the id "text", operations such as [setTextAppearance] and [makeIcon] will operate upon that [TextView]. */ - fun setContentView(contentView: View) { + public fun setContentView(contentView: View) { rotationLayout.removeAllViews() rotationLayout.addView(contentView) this.contentView = contentView @@ -123,7 +123,7 @@ class IconGenerator(private val context: Context) { * * @param degrees the amount the contents should be rotated, as a multiple of 90 degrees. */ - fun setContentRotation(degrees: Int) { + public fun setContentRotation(degrees: Int) { rotationLayout.setViewRotation(degrees) } @@ -132,21 +132,21 @@ 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 + public fun setRotation(degrees: Int) { + rotation = degrees.mod(360) / 90 } /** * @return u coordinate of the anchor, with rotation applied. */ - fun getAnchorU(): Float { + public fun getAnchorU(): Float { return rotateAnchor(anchorU, anchorV) } /** * @return v coordinate of the anchor, with rotation applied. */ - fun getAnchorV(): Float { + public fun getAnchorV(): Float { return rotateAnchor(anchorV, anchorU) } @@ -169,7 +169,7 @@ class IconGenerator(private val context: Context) { * * @param resid the identifier of the resource. */ - fun setTextAppearance(context: Context, resid: Int) { + public fun setTextAppearance(context: Context, resid: Int) { textView?.let { TextViewCompat.setTextAppearance(it, resid) } } @@ -179,14 +179,14 @@ class IconGenerator(private val context: Context) { * * @param resid the identifier of the resource. */ - fun setTextAppearance(resid: Int) { + public fun setTextAppearance(resid: Int) { setTextAppearance(context, resid) } /** * Sets the style of the icon. The style consists of a background and text appearance. */ - fun setStyle(style: Int) { + public fun setStyle(style: Int) { setColor(getStyleColor(style)) setTextAppearance(context, getTextStyle(style)) } @@ -196,7 +196,7 @@ class IconGenerator(private val context: Context) { * * @param color the color for the background tint. */ - fun setColor(color: Int) { + public fun setColor(color: Int) { bubbleDrawable.setColor(color) setBackground(bubbleDrawable) } @@ -206,7 +206,7 @@ class IconGenerator(private val context: Context) { * * @param background the Drawable to use as the background, or null to remove the background. */ - fun setBackground(background: Drawable?) { + public fun setBackground(background: Drawable?) { ViewCompat.setBackground(container, background) // Force setting of padding. @@ -229,18 +229,18 @@ class IconGenerator(private val context: Context) { * @param right the right padding in pixels. * @param bottom the bottom padding in pixels. */ - fun setContentPadding(left: Int, top: Int, right: Int, bottom: Int) { + public fun setContentPadding(left: Int, top: Int, right: Int, bottom: Int) { contentView?.setPadding(left, top, right, bottom) } - companion object { - const val STYLE_DEFAULT = 1 - const val STYLE_WHITE = 2 - const val STYLE_RED = 3 - const val STYLE_BLUE = 4 - const val STYLE_GREEN = 5 - const val STYLE_PURPLE = 6 - const val STYLE_ORANGE = 7 + public companion object { + public const val STYLE_DEFAULT: Int = 1 + public const val STYLE_WHITE: Int = 2 + public const val STYLE_RED: Int = 3 + public const val STYLE_BLUE: Int = 4 + public const val STYLE_GREEN: Int = 5 + public const val STYLE_PURPLE: Int = 6 + public const val STYLE_ORANGE: Int = 7 private fun getStyleColor(style: Int): Int { return when (style) { 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..f6a5496ed 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 @@ -25,9 +25,9 @@ import android.widget.FrameLayout * * May not work with padding. */ -class RotationLayout +public class RotationLayout @JvmOverloads - constructor( + public constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0, @@ -49,8 +49,8 @@ class RotationLayout /** * @param degrees the rotation, in degrees. */ - fun setViewRotation(degrees: Int) { - rotation = (degrees + 360) % 360 / 90 + public fun setViewRotation(degrees: Int) { + rotation = degrees.mod(360) / 90 } public override fun dispatchDraw(canvas: Canvas) { diff --git a/ui/src/main/java/com/google/maps/android/ui/SquareTextView.kt b/ui/src/main/java/com/google/maps/android/ui/SquareTextView.kt index c26381065..bca8af159 100644 --- a/ui/src/main/java/com/google/maps/android/ui/SquareTextView.kt +++ b/ui/src/main/java/com/google/maps/android/ui/SquareTextView.kt @@ -27,9 +27,9 @@ import kotlin.math.max * not, depending on the Inspection Settings. It's not really an error, just a warning. */ @SuppressLint("AppCompatCustomView") -class SquareTextView +public class SquareTextView @JvmOverloads - constructor( + public constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0, @@ -37,7 +37,7 @@ class SquareTextView private var offsetTop = 0 private var offsetLeft = 0 - override fun onMeasure( + public override fun onMeasure( widthMeasureSpec: Int, heightMeasureSpec: Int, ) { @@ -55,7 +55,7 @@ class SquareTextView setMeasuredDimension(dimension, dimension) } - override fun draw(canvas: Canvas) { + public override fun draw(canvas: Canvas) { canvas.translate(offsetLeft / 2f, offsetTop / 2f) super.draw(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) + } }