Skip to content

Commit f32d05d

Browse files
Shahroz16claude
andcommitted
feat: add core location types, config, and EventBus event
Add foundational location types and wiring needed by subsequent location features: - LocationModuleConfig: enableLocationTracking flag with Builder - LocationData / TrackLocationEvent in core Event sealed class - LocationSnapshot, AuthorizationStatus, LocationProviderError, LocationGranularity types in location module - LocationProviding interface abstracting platform location services - LocationRequestException for structured error propagation - DataPipeline subscription for TrackLocationEvent -> "Location Update" - LOCATION_UPDATE event name constant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 08d0c47 commit f32d05d

File tree

13 files changed

+229
-8
lines changed

13 files changed

+229
-8
lines changed

core/api/core.api

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ public final class io/customer/sdk/communication/Event$DeleteDeviceTokenEvent :
2727
public fun <init> ()V
2828
}
2929

30+
public final class io/customer/sdk/communication/Event$LocationData {
31+
public fun <init> (DD)V
32+
public final fun component1 ()D
33+
public final fun component2 ()D
34+
public final fun copy (DD)Lio/customer/sdk/communication/Event$LocationData;
35+
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$LocationData;DDILjava/lang/Object;)Lio/customer/sdk/communication/Event$LocationData;
36+
public fun equals (Ljava/lang/Object;)Z
37+
public final fun getLatitude ()D
38+
public final fun getLongitude ()D
39+
public fun hashCode ()I
40+
public fun toString ()Ljava/lang/String;
41+
}
42+
3043
public final class io/customer/sdk/communication/Event$ProfileIdentifiedEvent : io/customer/sdk/communication/Event {
3144
public fun <init> (Ljava/lang/String;)V
3245
public final fun component1 ()Ljava/lang/String;
@@ -80,6 +93,17 @@ public final class io/customer/sdk/communication/Event$TrackInAppMetricEvent : i
8093
public fun toString ()Ljava/lang/String;
8194
}
8295

96+
public final class io/customer/sdk/communication/Event$TrackLocationEvent : io/customer/sdk/communication/Event {
97+
public fun <init> (Lio/customer/sdk/communication/Event$LocationData;)V
98+
public final fun component1 ()Lio/customer/sdk/communication/Event$LocationData;
99+
public final fun copy (Lio/customer/sdk/communication/Event$LocationData;)Lio/customer/sdk/communication/Event$TrackLocationEvent;
100+
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$TrackLocationEvent;Lio/customer/sdk/communication/Event$LocationData;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$TrackLocationEvent;
101+
public fun equals (Ljava/lang/Object;)Z
102+
public final fun getLocation ()Lio/customer/sdk/communication/Event$LocationData;
103+
public fun hashCode ()I
104+
public fun toString ()Ljava/lang/String;
105+
}
106+
83107
public final class io/customer/sdk/communication/Event$TrackPushMetricEvent : io/customer/sdk/communication/Event {
84108
public fun <init> (Ljava/lang/String;Lio/customer/sdk/events/Metric;Ljava/lang/String;)V
85109
public final fun component1 ()Ljava/lang/String;

core/src/main/kotlin/io/customer/sdk/communication/Event.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,23 @@ sealed class Event {
4848
) : Event()
4949

5050
class DeleteDeviceTokenEvent : Event()
51+
52+
/**
53+
* Event emitted when a location update should be tracked.
54+
* Published by the Location module and consumed by DataPipeline
55+
* to send location data to Customer.io servers.
56+
*/
57+
data class TrackLocationEvent(
58+
val location: LocationData
59+
) : Event()
60+
61+
/**
62+
* Location data in a framework-agnostic format.
63+
* Used to pass location information between modules without
64+
* requiring Android location framework imports.
65+
*/
66+
data class LocationData(
67+
val latitude: Double,
68+
val longitude: Double
69+
)
5170
}

core/src/main/kotlin/io/customer/sdk/util/EventNames.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ object EventNames {
1111

1212
// Event name fired by AndroidLifecyclePlugin when app enters background
1313
const val APPLICATION_BACKGROUNDED = "Application Backgrounded"
14+
15+
// Event name for location updates tracked by the Location module
16+
const val LOCATION_UPDATE = "Location Update"
1417
}

datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,21 @@ class CustomerIO private constructor(
157157
eventBus.subscribe<Event.RegisterDeviceTokenEvent> {
158158
registerDeviceToken(deviceToken = it.token)
159159
}
160+
eventBus.subscribe<Event.TrackLocationEvent> {
161+
trackLocation(it)
162+
}
163+
}
164+
165+
private fun trackLocation(event: Event.TrackLocationEvent) {
166+
val location = event.location
167+
logger.debug("tracking location update: lat=${location.latitude}, lng=${location.longitude}")
168+
track(
169+
name = EventNames.LOCATION_UPDATE,
170+
properties = mapOf(
171+
"lat" to location.latitude,
172+
"lng" to location.longitude
173+
)
174+
)
160175
}
161176

162177
private fun migrateTrackingEvents() {

location/api/location.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
public final class io/customer/location/LocationModuleConfig : io/customer/sdk/core/module/CustomerIOModuleConfig {
2+
public synthetic fun <init> (ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
3+
public final fun getEnableLocationTracking ()Z
24
}
35

46
public final class io/customer/location/LocationModuleConfig$Builder : io/customer/sdk/core/module/CustomerIOModuleConfig$Builder {
57
public fun <init> ()V
68
public fun build ()Lio/customer/location/LocationModuleConfig;
79
public synthetic fun build ()Lio/customer/sdk/core/module/CustomerIOModuleConfig;
10+
public final fun setEnableLocationTracking (Z)Lio/customer/location/LocationModuleConfig$Builder;
811
}
912

1013
public final class io/customer/location/ModuleLocation : io/customer/sdk/core/module/CustomerIOModule {
1114
public static final field Companion Lio/customer/location/ModuleLocation$Companion;
1215
public static final field MODULE_NAME Ljava/lang/String;
16+
public fun <init> ()V
1317
public fun <init> (Lio/customer/location/LocationModuleConfig;)V
18+
public synthetic fun <init> (Lio/customer/location/LocationModuleConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1419
public fun getModuleConfig ()Lio/customer/location/LocationModuleConfig;
1520
public synthetic fun getModuleConfig ()Lio/customer/sdk/core/module/CustomerIOModuleConfig;
1621
public fun getModuleName ()Ljava/lang/String;

location/src/main/kotlin/io/customer/location/LocationModuleConfig.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,33 @@ import io.customer.sdk.core.module.CustomerIOModuleConfig
66
* Location module configurations that can be used to customize
77
* location tracking behavior based on the provided configurations.
88
*/
9-
class LocationModuleConfig private constructor() : CustomerIOModuleConfig {
9+
class LocationModuleConfig private constructor(
10+
/**
11+
* Whether location tracking is enabled.
12+
*
13+
* When false, the location module is effectively disabled and all location
14+
* tracking operations will no-op silently.
15+
*/
16+
val enableLocationTracking: Boolean
17+
) : CustomerIOModuleConfig {
1018

1119
class Builder : CustomerIOModuleConfig.Builder<LocationModuleConfig> {
20+
private var enableLocationTracking: Boolean = true
21+
22+
/**
23+
* Sets whether location tracking is enabled.
24+
* When disabled, all location operations will no-op silently.
25+
* Default is true.
26+
*/
27+
fun setEnableLocationTracking(enable: Boolean): Builder {
28+
this.enableLocationTracking = enable
29+
return this
30+
}
31+
1232
override fun build(): LocationModuleConfig {
13-
return LocationModuleConfig()
33+
return LocationModuleConfig(
34+
enableLocationTracking = enableLocationTracking
35+
)
1436
}
1537
}
1638
}

location/src/main/kotlin/io/customer/location/ModuleLocation.kt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,28 @@ import io.customer.sdk.core.module.CustomerIOModule
77
* Location module for Customer.io SDK.
88
*
99
* This module provides location tracking capabilities including:
10-
* - Permission and consent management
11-
* - One-shot location capture
12-
* - Continuous location tracking
10+
* - Manual location setting from host app's existing location system
11+
* - One-shot SDK-managed location capture
12+
*
13+
* Usage:
14+
* ```
15+
* val config = CustomerIOConfigBuilder(appContext, "your-api-key")
16+
* .addCustomerIOModule(
17+
* ModuleLocation(
18+
* LocationModuleConfig.Builder()
19+
* .setEnableLocationTracking(true)
20+
* .build()
21+
* )
22+
* )
23+
* .build()
24+
*
25+
* CustomerIO.initialize(config)
26+
* ```
1327
*/
14-
class ModuleLocation(
15-
config: LocationModuleConfig
28+
class ModuleLocation @JvmOverloads constructor(
29+
override val moduleConfig: LocationModuleConfig = LocationModuleConfig.Builder().build()
1630
) : CustomerIOModule<LocationModuleConfig> {
1731
override val moduleName: String = MODULE_NAME
18-
override val moduleConfig: LocationModuleConfig = config
1932

2033
override fun initialize() {
2134
// Module initialization will be implemented in future PRs
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.customer.location.provider
2+
3+
import io.customer.location.type.AuthorizationStatus
4+
import io.customer.location.type.LocationGranularity
5+
import io.customer.location.type.LocationSnapshot
6+
7+
/**
8+
* Abstracts system location services. Implementations wrap platform-specific
9+
* location providers (e.g. FusedLocationProviderClient).
10+
*
11+
* This component does not request location permission. The host app must handle
12+
* runtime permission requests and only call location APIs once authorized.
13+
*/
14+
internal interface LocationProvider {
15+
/**
16+
* One-shot location request. Returns the location result directly.
17+
*
18+
* @param granularity desired accuracy level for the request
19+
* @return the captured location snapshot
20+
* @throws LocationRequestException if location could not be obtained
21+
*/
22+
suspend fun requestLocation(granularity: LocationGranularity): LocationSnapshot
23+
24+
/**
25+
* Cancels any in-flight location request. Idempotent - safe to call
26+
* even when no request is in progress.
27+
*/
28+
suspend fun cancelRequestLocation()
29+
30+
/**
31+
* Current authorization state for location access.
32+
* Used for pre-checks before requesting location.
33+
*/
34+
suspend fun currentAuthorizationStatus(): AuthorizationStatus
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.customer.location.provider
2+
3+
import io.customer.location.type.LocationProviderError
4+
5+
/**
6+
* Exception thrown when a location request fails.
7+
* Wraps [LocationProviderError] to provide structured error information.
8+
*/
9+
internal class LocationRequestException(
10+
val error: LocationProviderError,
11+
message: String = "Location request failed: $error",
12+
cause: Throwable? = null
13+
) : Exception(message, cause)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.customer.location.type
2+
3+
/**
4+
* Authorization status for location access.
5+
* Maps Android runtime permission states to SDK-level values
6+
* without depending on Android framework classes.
7+
*
8+
* Note: On Android, [DENIED] covers both "never asked" and "explicitly denied"
9+
* because [android.content.pm.PackageManager.checkPermission] cannot distinguish
10+
* between the two without an Activity context.
11+
*/
12+
internal enum class AuthorizationStatus {
13+
/** Permission not granted (either never asked or explicitly denied). */
14+
DENIED,
15+
16+
/** Foreground-only location access granted. */
17+
AUTHORIZED_FOREGROUND,
18+
19+
/** Background + foreground location access granted. */
20+
AUTHORIZED_BACKGROUND;
21+
22+
/** Whether the app is authorized to use location (foreground or background). */
23+
val isAuthorized: Boolean
24+
get() = this == AUTHORIZED_FOREGROUND || this == AUTHORIZED_BACKGROUND
25+
}

0 commit comments

Comments
 (0)