From 5052ee28d36769f0b1e03337e4aedcde4002e97e Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Thu, 22 Jan 2026 15:52:21 +0500 Subject: [PATCH 1/4] added permission setup --- location/api/location.api | 77 +++++++++ .../io/customer/location/ModuleLocation.kt | 148 +++++++++++++++++- .../consent/LocationTrackingEligibility.kt | 33 ++++ .../customer/location/di/LocationComponent.kt | 49 ++++++ .../customer/location/error/LocationError.kt | 61 ++++++++ .../permission/LocationPermissionStatus.kt | 24 +++ .../permission/LocationPermissionsHelper.kt | 89 +++++++++++ .../location/store/LocationPreferenceStore.kt | 39 +++++ .../tracking/TrackingEligibilityChecker.kt | 68 ++++++++ 9 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt create mode 100644 location/src/main/kotlin/io/customer/location/di/LocationComponent.kt create mode 100644 location/src/main/kotlin/io/customer/location/error/LocationError.kt create mode 100644 location/src/main/kotlin/io/customer/location/permission/LocationPermissionStatus.kt create mode 100644 location/src/main/kotlin/io/customer/location/permission/LocationPermissionsHelper.kt create mode 100644 location/src/main/kotlin/io/customer/location/store/LocationPreferenceStore.kt create mode 100644 location/src/main/kotlin/io/customer/location/tracking/TrackingEligibilityChecker.kt diff --git a/location/api/location.api b/location/api/location.api index b1e5f5291..ded171d29 100644 --- a/location/api/location.api +++ b/location/api/location.api @@ -11,14 +11,91 @@ public final class io/customer/location/ModuleLocation : io/customer/sdk/core/mo public static final field Companion Lio/customer/location/ModuleLocation$Companion; public static final field MODULE_NAME Ljava/lang/String; public fun (Lio/customer/location/LocationModuleConfig;)V + public final fun canTrackLocation ()Z public fun getModuleConfig ()Lio/customer/location/LocationModuleConfig; public synthetic fun getModuleConfig ()Lio/customer/sdk/core/module/CustomerIOModuleConfig; public fun getModuleName ()Ljava/lang/String; + public final fun getTrackingEligibility ()Lio/customer/location/consent/LocationTrackingEligibility; public fun initialize ()V public static final fun instance ()Lio/customer/location/ModuleLocation; + public final fun isLocationServicesEnabled ()Z + public final fun isTrackingEnabled ()Z + public final fun permissionStatus ()Lio/customer/location/permission/LocationPermissionStatus; + public final fun setTrackingEnabled (Z)V } public final class io/customer/location/ModuleLocation$Companion { public final fun instance ()Lio/customer/location/ModuleLocation; } +public abstract class io/customer/location/consent/LocationTrackingEligibility { +} + +public final class io/customer/location/consent/LocationTrackingEligibility$Eligible : io/customer/location/consent/LocationTrackingEligibility { + public static final field INSTANCE Lio/customer/location/consent/LocationTrackingEligibility$Eligible; +} + +public final class io/customer/location/consent/LocationTrackingEligibility$LocationServicesDisabled : io/customer/location/consent/LocationTrackingEligibility { + public static final field INSTANCE Lio/customer/location/consent/LocationTrackingEligibility$LocationServicesDisabled; +} + +public final class io/customer/location/consent/LocationTrackingEligibility$NotEnabled : io/customer/location/consent/LocationTrackingEligibility { + public static final field INSTANCE Lio/customer/location/consent/LocationTrackingEligibility$NotEnabled; +} + +public final class io/customer/location/consent/LocationTrackingEligibility$PermissionRequired : io/customer/location/consent/LocationTrackingEligibility { + public static final field INSTANCE Lio/customer/location/consent/LocationTrackingEligibility$PermissionRequired; +} + +public abstract class io/customer/location/error/LocationError { +} + +public final class io/customer/location/error/LocationError$LocationServicesDisabled : io/customer/location/error/LocationError { + public static final field INSTANCE Lio/customer/location/error/LocationError$LocationServicesDisabled; + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/location/error/LocationError$PermissionDenied : io/customer/location/error/LocationError { + public static final field INSTANCE Lio/customer/location/error/LocationError$PermissionDenied; + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/location/error/LocationError$ServiceUnavailable : io/customer/location/error/LocationError { + public static final field INSTANCE Lio/customer/location/error/LocationError$ServiceUnavailable; + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/location/error/LocationError$Timeout : io/customer/location/error/LocationError { + public static final field INSTANCE Lio/customer/location/error/LocationError$Timeout; + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/location/error/LocationError$TrackingDisabled : io/customer/location/error/LocationError { + public static final field INSTANCE Lio/customer/location/error/LocationError$TrackingDisabled; + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/location/error/LocationError$Unknown : io/customer/location/error/LocationError { + public fun ()V + public fun (Ljava/lang/Throwable;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Throwable;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Throwable;Ljava/lang/String;)Lio/customer/location/error/LocationError$Unknown; + public static synthetic fun copy$default (Lio/customer/location/error/LocationError$Unknown;Ljava/lang/Throwable;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/location/error/LocationError$Unknown; + public fun equals (Ljava/lang/Object;)Z + public final fun getCause ()Ljava/lang/Throwable; + public final fun getMessage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/location/permission/LocationPermissionStatus : java/lang/Enum { + public static final field AUTHORIZED Lio/customer/location/permission/LocationPermissionStatus; + public static final field DENIED Lio/customer/location/permission/LocationPermissionStatus; + public static final field NOT_DETERMINED Lio/customer/location/permission/LocationPermissionStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/customer/location/permission/LocationPermissionStatus; + public static fun values ()[Lio/customer/location/permission/LocationPermissionStatus; +} + diff --git a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt index 0f55e50c1..af7c91e8e 100644 --- a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt +++ b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt @@ -1,5 +1,8 @@ package io.customer.location +import io.customer.location.consent.LocationTrackingEligibility +import io.customer.location.di.LocationComponent +import io.customer.location.permission.LocationPermissionStatus import io.customer.sdk.core.di.SDKComponent import io.customer.sdk.core.module.CustomerIOModule @@ -8,8 +11,32 @@ import io.customer.sdk.core.module.CustomerIOModule * * This module provides location tracking capabilities including: * - Permission and consent management - * - One-shot location capture - * - Continuous location tracking + * - One-shot location capture (trackOnce) + * - Continuous location tracking (start/stop) + * - Automatic location tracking on initialization and app lifecycle + * + * ## Usage + * + * ```kotlin + * // Get the location module instance + * val location = ModuleLocation.instance() + * + * // Enable location tracking + * location.setTrackingEnabled(enabled = true) + * + * // Check permission status + * when (location.permissionStatus()) { + * LocationPermissionStatus.NOT_DETERMINED -> // Request permission + * LocationPermissionStatus.DENIED -> // Guide user to settings + * LocationPermissionStatus.AUTHORIZED -> // Can track location + * } + * ``` + * + * ## Cross-Platform API + * + * This module exposes a unified API that is consistent across Android and iOS, + * enabling wrapper SDKs (React Native, Flutter, Expo) to use a single interface. + * Platform-specific details are handled internally. */ class ModuleLocation( config: LocationModuleConfig @@ -17,17 +44,130 @@ class ModuleLocation( override val moduleName: String = MODULE_NAME override val moduleConfig: LocationModuleConfig = config + private val logger = SDKComponent.logger + private val trackingEligibilityChecker by lazy { LocationComponent.trackingEligibilityChecker } + private val permissionsHelper by lazy { LocationComponent.permissionsHelper } + override fun initialize() { - // Module initialization will be implemented in future PRs + logger.debug("Location module initialized") + } + + // region Public API - Cross-Platform + + /** + * Gets the current location permission status. + * + * This method only checks the current state - it does NOT request permissions. + * The app is responsible for requesting permissions through platform-specific APIs. + * + * @return The current [LocationPermissionStatus] + */ + fun permissionStatus(): LocationPermissionStatus { + val context = SDKComponent.android().applicationContext + return permissionsHelper.getPermissionStatus(context) + } + + /** + * Checks if device location services are enabled. + * + * If this returns false, the user needs to enable location services + * in their device settings before location tracking can work. + * + * @return true if location services are enabled, false otherwise + */ + fun isLocationServicesEnabled(): Boolean { + val context = SDKComponent.android().applicationContext + return permissionsHelper.locationServicesEnabled(context) + } + + /** + * Sets whether location tracking is enabled. + * + * Location tracking requires both opt-in AND permission. + * Call this method to enable/disable location tracking from your app. + * + * @param enabled true to enable tracking, false to disable + */ + fun setTrackingEnabled(enabled: Boolean) { + trackingEligibilityChecker.isTrackingEnabled = enabled + logger.debug("Location tracking enabled: $enabled") } + /** + * Checks if location tracking is enabled. + * + * @return true if tracking is enabled, false otherwise + */ + fun isTrackingEnabled(): Boolean { + return trackingEligibilityChecker.isTrackingEnabled + } + + /** + * Checks if location tracking is currently allowed. + * + * Returns true only if: + * 1. Tracking has been enabled via [setTrackingEnabled] + * 2. Location permission is granted + * 3. Device location services are enabled + * + * @return true if location tracking can proceed, false otherwise + */ + fun canTrackLocation(): Boolean { + return trackingEligibilityChecker.canTrackLocation() + } + + /** + * Gets the current tracking eligibility status. + * + * Useful for providing user feedback about why tracking is disabled. + * + * @return The current [LocationTrackingEligibility] status + */ + fun getTrackingEligibility(): LocationTrackingEligibility { + return trackingEligibilityChecker.getTrackingEligibility() + } + + // endregion + + // region Internal API - Android-Specific + + /** + * Gets the list of permissions required for location tracking. + * + * This is an Android-specific helper for requesting permissions. + * Use with `ActivityCompat.requestPermissions()`. + * + * @return Array of permission strings (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) + */ + internal fun getRequiredPermissions(): Array { + return permissionsHelper.getRequiredPermissions() + } + + /** + * Marks that permission has been requested. + * + * This is an Android-specific helper to track permission request state. + * Call this after showing the permission dialog to help the SDK + * distinguish between "not determined" and "denied" states. + */ + internal fun markPermissionRequested() { + permissionsHelper.markPermissionRequested() + } + + // endregion + companion object { const val MODULE_NAME: String = "Location" + /** + * Gets the singleton instance of the Location module. + * + * @throws IllegalStateException if the module has not been initialized + */ @JvmStatic fun instance(): ModuleLocation { return SDKComponent.modules[MODULE_NAME] as? ModuleLocation - ?: throw IllegalStateException("ModuleLocation not initialized") + ?: throw IllegalStateException("ModuleLocation not initialized. Make sure to add the location module when initializing CustomerIO.") } } } diff --git a/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt b/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt new file mode 100644 index 000000000..9be246bf7 --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt @@ -0,0 +1,33 @@ +package io.customer.location.consent + +/** + * Represents the current eligibility status for location tracking. + * + * This sealed class provides a unified way to understand why location + * tracking may or may not be available, enabling wrapper SDKs to + * provide appropriate user feedback. + */ +sealed class LocationTrackingEligibility { + /** + * Location tracking is eligible - tracking enabled, permission granted, services enabled. + */ + object Eligible : LocationTrackingEligibility() + + /** + * Location tracking has not been enabled via [ModuleLocation.setTrackingEnabled]. + * The app needs to call setTrackingEnabled(true) to enable tracking. + */ + object NotEnabled : LocationTrackingEligibility() + + /** + * Location permission has not been granted by the user. + * The app needs to request location permission from the user. + */ + object PermissionRequired : LocationTrackingEligibility() + + /** + * Device location services are disabled in system settings. + * The user needs to enable location services in their device settings. + */ + object LocationServicesDisabled : LocationTrackingEligibility() +} diff --git a/location/src/main/kotlin/io/customer/location/di/LocationComponent.kt b/location/src/main/kotlin/io/customer/location/di/LocationComponent.kt new file mode 100644 index 000000000..f316af56c --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/di/LocationComponent.kt @@ -0,0 +1,49 @@ +package io.customer.location.di + +import io.customer.location.permission.LocationPermissionsHelper +import io.customer.location.store.LocationPreferenceStore +import io.customer.location.tracking.TrackingEligibilityChecker +import io.customer.sdk.core.di.DiGraph +import io.customer.sdk.core.di.SDKComponent + +/** + * Dependency injection component for the Location module. + * + * Provides singleton instances of location-related dependencies + * that can be accessed throughout the module. + */ +internal object LocationComponent : DiGraph() { + + private val androidComponent + get() = SDKComponent.android() + + private val applicationContext + get() = androidComponent.applicationContext + + /** + * Preference store for location module settings. + */ + val preferenceStore: LocationPreferenceStore + get() = singleton { LocationPreferenceStore(applicationContext) } + + /** + * Permission helper for checking location permission status. + */ + val permissionsHelper: LocationPermissionsHelper + get() = singleton { LocationPermissionsHelper(preferenceStore) } + + /** + * Checker for determining if location tracking is eligible. + */ + val trackingEligibilityChecker: TrackingEligibilityChecker + get() = singleton { TrackingEligibilityChecker(applicationContext, preferenceStore, permissionsHelper) } + + /** + * Resets all location component dependencies. + * Called when the SDK is reset. + */ + override fun reset() { + preferenceStore.clearAll() + super.reset() + } +} diff --git a/location/src/main/kotlin/io/customer/location/error/LocationError.kt b/location/src/main/kotlin/io/customer/location/error/LocationError.kt new file mode 100644 index 000000000..3f02ac959 --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/error/LocationError.kt @@ -0,0 +1,61 @@ +package io.customer.location.error + +/** + * Represents errors that can occur during location operations. + * + * These error types are platform-agnostic and consistent across Android and iOS, + * enabling wrapper SDKs to handle errors uniformly. + */ +sealed class LocationError { + /** + * Location permission was denied by the user. + */ + object PermissionDenied : LocationError() { + override fun toString(): String = "LocationError.PermissionDenied" + } + + /** + * Location tracking has not been enabled via [ModuleLocation.setTrackingEnabled]. + */ + object TrackingDisabled : LocationError() { + override fun toString(): String = "LocationError.TrackingDisabled" + } + + /** + * Device location services are disabled in system settings. + */ + object LocationServicesDisabled : LocationError() { + override fun toString(): String = "LocationError.LocationServicesDisabled" + } + + /** + * Location service is not available on the device. + * + * This covers platform-specific unavailability: + * - Android: Google Play Services not available + * - iOS: CoreLocation service issues + */ + object ServiceUnavailable : LocationError() { + override fun toString(): String = "LocationError.ServiceUnavailable" + } + + /** + * Location request timed out without receiving a location. + */ + object Timeout : LocationError() { + override fun toString(): String = "LocationError.Timeout" + } + + /** + * An unknown error occurred during location operations. + * + * @property cause The underlying exception that caused the error, if available. + * @property message A descriptive message about the error. + */ + data class Unknown( + val cause: Throwable? = null, + val message: String? = null + ) : LocationError() { + override fun toString(): String = "LocationError.Unknown(message=$message, cause=$cause)" + } +} diff --git a/location/src/main/kotlin/io/customer/location/permission/LocationPermissionStatus.kt b/location/src/main/kotlin/io/customer/location/permission/LocationPermissionStatus.kt new file mode 100644 index 000000000..e7a9a14c9 --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/permission/LocationPermissionStatus.kt @@ -0,0 +1,24 @@ +package io.customer.location.permission + +/** + * Represents the current state of location permissions for the app. + */ +enum class LocationPermissionStatus { + /** + * Permission has never been requested. + * The app should prompt the user to grant permission. + */ + NOT_DETERMINED, + + /** + * User has explicitly denied location permission. + * The app should guide user to settings to enable permission. + */ + DENIED, + + /** + * User has granted location permission. + * Location can be accessed while the app is in foreground. + */ + AUTHORIZED +} diff --git a/location/src/main/kotlin/io/customer/location/permission/LocationPermissionsHelper.kt b/location/src/main/kotlin/io/customer/location/permission/LocationPermissionsHelper.kt new file mode 100644 index 000000000..92c10a17c --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/permission/LocationPermissionsHelper.kt @@ -0,0 +1,89 @@ +package io.customer.location.permission + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.location.LocationManager +import androidx.core.content.ContextCompat +import androidx.core.location.LocationManagerCompat +import io.customer.location.store.LocationPreferenceStore + +/** + * Helper class for checking location permission status. + * + * This class only checks permissions - it does NOT request them. + * The app is responsible for requesting permissions using + * ActivityCompat.requestPermissions(). + */ +internal class LocationPermissionsHelper( + private val preferenceStore: LocationPreferenceStore +) { + + /** + * Checks if fine location permission (ACCESS_FINE_LOCATION) is granted. + */ + fun fineLocationPermissionGranted(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * Checks if coarse location permission (ACCESS_COARSE_LOCATION) is granted. + */ + fun coarseLocationPermissionGranted(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * Checks if any location permission (fine or coarse) is granted. + */ + fun anyLocationPermissionGranted(context: Context): Boolean { + return fineLocationPermissionGranted(context) || coarseLocationPermissionGranted(context) + } + + /** + * Checks if device location services are enabled. + */ + fun locationServicesEnabled(context: Context): Boolean { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager + ?: return false + return LocationManagerCompat.isLocationEnabled(locationManager) + } + + /** + * Gets the current permission status. + */ + fun getPermissionStatus(context: Context): LocationPermissionStatus { + if (!anyLocationPermissionGranted(context)) { + return if (preferenceStore.hasRequestedPermission) { + LocationPermissionStatus.DENIED + } else { + LocationPermissionStatus.NOT_DETERMINED + } + } + return LocationPermissionStatus.AUTHORIZED + } + + /** + * Marks that permission has been requested. + * Call this after showing the permission dialog. + */ + fun markPermissionRequested() { + preferenceStore.hasRequestedPermission = true + } + + /** + * Gets the list of permissions required for location tracking. + */ + fun getRequiredPermissions(): Array { + return arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } +} diff --git a/location/src/main/kotlin/io/customer/location/store/LocationPreferenceStore.kt b/location/src/main/kotlin/io/customer/location/store/LocationPreferenceStore.kt new file mode 100644 index 000000000..abbe13c3a --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/store/LocationPreferenceStore.kt @@ -0,0 +1,39 @@ +package io.customer.location.store + +import android.content.Context +import androidx.core.content.edit +import io.customer.sdk.data.store.PreferenceStore +import io.customer.sdk.data.store.read + +/** + * Preference store for location module settings. + * + * Extends the SDK's PreferenceStore pattern for consistent storage handling. + */ +internal class LocationPreferenceStore( + context: Context +) : PreferenceStore(context) { + + override val prefsName: String = "io.customer.location.${context.packageName}" + + /** + * Whether location tracking is enabled by the app. + * This is the app-level opt-in, separate from OS permissions. + */ + var isTrackingEnabled: Boolean + get() = prefs.read { getBoolean(KEY_TRACKING_ENABLED, false) } ?: false + set(value) = prefs.edit { putBoolean(KEY_TRACKING_ENABLED, value) } + + /** + * Whether we've already requested location permission from the user. + * Used to distinguish between "not determined" and "denied" states. + */ + var hasRequestedPermission: Boolean + get() = prefs.read { getBoolean(KEY_PERMISSION_REQUESTED, false) } ?: false + set(value) = prefs.edit { putBoolean(KEY_PERMISSION_REQUESTED, value) } + + companion object { + private const val KEY_TRACKING_ENABLED = "tracking_enabled" + private const val KEY_PERMISSION_REQUESTED = "permission_requested" + } +} diff --git a/location/src/main/kotlin/io/customer/location/tracking/TrackingEligibilityChecker.kt b/location/src/main/kotlin/io/customer/location/tracking/TrackingEligibilityChecker.kt new file mode 100644 index 000000000..6c706a931 --- /dev/null +++ b/location/src/main/kotlin/io/customer/location/tracking/TrackingEligibilityChecker.kt @@ -0,0 +1,68 @@ +package io.customer.location.tracking + +import android.content.Context +import io.customer.location.consent.LocationTrackingEligibility +import io.customer.location.permission.LocationPermissionsHelper +import io.customer.location.store.LocationPreferenceStore + +/** + * Determines eligibility for location tracking based on multiple factors. + * + * Location tracking requires: + * 1. Tracking enabled via [setTrackingEnabled] - explicit opt-in from the app + * 2. OS permission - granted by the user through system dialogs + * 3. Location services - enabled in device settings + * + * This follows the principle of explicit consent: no location data + * is collected until the app explicitly enables tracking. + */ +internal class TrackingEligibilityChecker( + private val context: Context, + private val preferenceStore: LocationPreferenceStore, + private val permissionsHelper: LocationPermissionsHelper +) { + + /** + * Gets or sets whether location tracking is enabled. + * + * When set to true, the SDK is allowed to track location + * (if permissions are also granted). + * When set to false, all location tracking is disabled. + */ + var isTrackingEnabled: Boolean + get() = preferenceStore.isTrackingEnabled + set(value) { + preferenceStore.isTrackingEnabled = value + } + + /** + * Checks if location tracking is currently allowed. + * + * Returns true only if: + * 1. Tracking has been enabled via [isTrackingEnabled] + * 2. Location permission is granted + * 3. Device location services are enabled + */ + fun canTrackLocation(): Boolean { + if (!isTrackingEnabled) return false + if (!permissionsHelper.anyLocationPermissionGranted(context)) return false + if (!permissionsHelper.locationServicesEnabled(context)) return false + return true + } + + /** + * Gets the current tracking eligibility status with reason. + * + * Useful for providing user feedback about why tracking is disabled. + */ + fun getTrackingEligibility(): LocationTrackingEligibility { + return when { + !isTrackingEnabled -> LocationTrackingEligibility.NotEnabled + !permissionsHelper.anyLocationPermissionGranted(context) -> + LocationTrackingEligibility.PermissionRequired + !permissionsHelper.locationServicesEnabled(context) -> + LocationTrackingEligibility.LocationServicesDisabled + else -> LocationTrackingEligibility.Eligible + } + } +} From 4306541acda93bb2e88dbf3a134ac6801e2dfd72 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Thu, 22 Jan 2026 18:14:30 +0500 Subject: [PATCH 2/4] updated API --- location/api/location.api | 4 ++-- .../main/kotlin/io/customer/location/ModuleLocation.kt | 8 ++++---- .../location/consent/LocationTrackingEligibility.kt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/location/api/location.api b/location/api/location.api index ded171d29..8b86df3c4 100644 --- a/location/api/location.api +++ b/location/api/location.api @@ -19,9 +19,9 @@ public final class io/customer/location/ModuleLocation : io/customer/sdk/core/mo public fun initialize ()V public static final fun instance ()Lio/customer/location/ModuleLocation; public final fun isLocationServicesEnabled ()Z - public final fun isTrackingEnabled ()Z + public final fun isLocationTrackingEnabled ()Z public final fun permissionStatus ()Lio/customer/location/permission/LocationPermissionStatus; - public final fun setTrackingEnabled (Z)V + public final fun setLocationTrackingEnabled (Z)V } public final class io/customer/location/ModuleLocation$Companion { diff --git a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt index af7c91e8e..26a262744 100644 --- a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt +++ b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt @@ -22,7 +22,7 @@ import io.customer.sdk.core.module.CustomerIOModule * val location = ModuleLocation.instance() * * // Enable location tracking - * location.setTrackingEnabled(enabled = true) + * location.setLocationTrackingEnabled(enabled = true) * * // Check permission status * when (location.permissionStatus()) { @@ -88,7 +88,7 @@ class ModuleLocation( * * @param enabled true to enable tracking, false to disable */ - fun setTrackingEnabled(enabled: Boolean) { + fun setLocationTrackingEnabled(enabled: Boolean) { trackingEligibilityChecker.isTrackingEnabled = enabled logger.debug("Location tracking enabled: $enabled") } @@ -98,7 +98,7 @@ class ModuleLocation( * * @return true if tracking is enabled, false otherwise */ - fun isTrackingEnabled(): Boolean { + fun isLocationTrackingEnabled(): Boolean { return trackingEligibilityChecker.isTrackingEnabled } @@ -106,7 +106,7 @@ class ModuleLocation( * Checks if location tracking is currently allowed. * * Returns true only if: - * 1. Tracking has been enabled via [setTrackingEnabled] + * 1. Tracking has been enabled via [setLocationTrackingEnabled] * 2. Location permission is granted * 3. Device location services are enabled * diff --git a/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt b/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt index 9be246bf7..119374e42 100644 --- a/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt +++ b/location/src/main/kotlin/io/customer/location/consent/LocationTrackingEligibility.kt @@ -14,7 +14,7 @@ sealed class LocationTrackingEligibility { object Eligible : LocationTrackingEligibility() /** - * Location tracking has not been enabled via [ModuleLocation.setTrackingEnabled]. + * Location tracking has not been enabled via [ModuleLocation.setLocationTrackingEnabled]. * The app needs to call setTrackingEnabled(true) to enable tracking. */ object NotEnabled : LocationTrackingEligibility() From 9b347b6e678146c62339c80f5b19d6b62562ee23 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Fri, 23 Jan 2026 00:13:47 +0500 Subject: [PATCH 3/4] fixes --- location/api/location.api | 1 + .../io/customer/location/ModuleLocation.kt | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/location/api/location.api b/location/api/location.api index 8b86df3c4..b684e28ef 100644 --- a/location/api/location.api +++ b/location/api/location.api @@ -15,6 +15,7 @@ public final class io/customer/location/ModuleLocation : io/customer/sdk/core/mo public fun getModuleConfig ()Lio/customer/location/LocationModuleConfig; public synthetic fun getModuleConfig ()Lio/customer/sdk/core/module/CustomerIOModuleConfig; public fun getModuleName ()Ljava/lang/String; + public final fun getRequiredPermissions ()[Ljava/lang/String; public final fun getTrackingEligibility ()Lio/customer/location/consent/LocationTrackingEligibility; public fun initialize ()V public static final fun instance ()Lio/customer/location/ModuleLocation; diff --git a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt index 26a262744..60babeff6 100644 --- a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt +++ b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt @@ -3,6 +3,8 @@ package io.customer.location import io.customer.location.consent.LocationTrackingEligibility import io.customer.location.di.LocationComponent import io.customer.location.permission.LocationPermissionStatus +import io.customer.sdk.communication.Event +import io.customer.sdk.communication.subscribe import io.customer.sdk.core.di.SDKComponent import io.customer.sdk.core.module.CustomerIOModule @@ -45,11 +47,17 @@ class ModuleLocation( override val moduleConfig: LocationModuleConfig = config private val logger = SDKComponent.logger + private val eventBus = SDKComponent.eventBus private val trackingEligibilityChecker by lazy { LocationComponent.trackingEligibilityChecker } private val permissionsHelper by lazy { LocationComponent.permissionsHelper } override fun initialize() { logger.debug("Location module initialized") + + eventBus.subscribe { + logger.debug("Resetting location module state") + LocationComponent.reset() + } } // region Public API - Cross-Platform @@ -129,7 +137,7 @@ class ModuleLocation( // endregion - // region Internal API - Android-Specific + // region Public API - Android-Specific /** * Gets the list of permissions required for location tracking. @@ -137,21 +145,15 @@ class ModuleLocation( * This is an Android-specific helper for requesting permissions. * Use with `ActivityCompat.requestPermissions()`. * - * @return Array of permission strings (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) - */ - internal fun getRequiredPermissions(): Array { - return permissionsHelper.getRequiredPermissions() - } - - /** - * Marks that permission has been requested. + * Note: Calling this method marks that permissions are being requested, + * which helps the SDK distinguish between "not determined" and "denied" states. * - * This is an Android-specific helper to track permission request state. - * Call this after showing the permission dialog to help the SDK - * distinguish between "not determined" and "denied" states. + * @return Array of permission strings (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) */ - internal fun markPermissionRequested() { + fun getRequiredPermissions(): Array { + // Auto-mark as requested since the app is getting permissions to request them permissionsHelper.markPermissionRequested() + return permissionsHelper.getRequiredPermissions() } // endregion From 9d3060486ecef6f13ad80c4b53bc434f268ab4d9 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Fri, 23 Jan 2026 01:19:55 +0500 Subject: [PATCH 4/4] fix getter --- location/api/location.api | 1 + .../io/customer/location/ModuleLocation.kt | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/location/api/location.api b/location/api/location.api index b684e28ef..8c993f3a1 100644 --- a/location/api/location.api +++ b/location/api/location.api @@ -21,6 +21,7 @@ public final class io/customer/location/ModuleLocation : io/customer/sdk/core/mo public static final fun instance ()Lio/customer/location/ModuleLocation; public final fun isLocationServicesEnabled ()Z public final fun isLocationTrackingEnabled ()Z + public final fun onPermissionResult ()V public final fun permissionStatus ()Lio/customer/location/permission/LocationPermissionStatus; public final fun setLocationTrackingEnabled (Z)V } diff --git a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt index 60babeff6..3cc1b863b 100644 --- a/location/src/main/kotlin/io/customer/location/ModuleLocation.kt +++ b/location/src/main/kotlin/io/customer/location/ModuleLocation.kt @@ -145,17 +145,28 @@ class ModuleLocation( * This is an Android-specific helper for requesting permissions. * Use with `ActivityCompat.requestPermissions()`. * - * Note: Calling this method marks that permissions are being requested, - * which helps the SDK distinguish between "not determined" and "denied" states. - * * @return Array of permission strings (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) */ fun getRequiredPermissions(): Array { - // Auto-mark as requested since the app is getting permissions to request them - permissionsHelper.markPermissionRequested() return permissionsHelper.getRequiredPermissions() } + /** + * Call this after requesting location permissions from the user. + * + * This helps the SDK distinguish between [LocationPermissionStatus.NOT_DETERMINED] + * (never asked) and [LocationPermissionStatus.DENIED] (user explicitly denied). + * + * Example: + * ```kotlin + * ActivityCompat.requestPermissions(activity, location.getRequiredPermissions(), REQ_CODE) + * location.onPermissionResult() // Call after requesting + * ``` + */ + fun onPermissionResult() { + permissionsHelper.markPermissionRequested() + } + // endregion companion object {