PhilM
PhilM

Reputation: 405

Android/ Kotlin : geofencingEvent.geofenceTransition stuck at 2

I am looking into geofencing.

geofencingEvent.geofenceTransition seems to trigger 2 (Exit) even if I set the geofence several kilometers away.

Test environments:

  1. Emulators;
  2. Phone (S9)

Things I thought could be the cause:

The radius. Maybe the radius is not read properly, but the database does specify a 50m radius and when displayed on the map the radius seems accurate.

The notification system: Seems to be easiest culprit. However, the log within onReceive() also confirms that the user has exited the Geofence, even though he never entered it.

2024-06-22 18:08:51.982  9613-9613  GeofenceBr...stReceiver com.example.geofenceapp              D  User exits the geofence 

onReceive() might be wrong: Thats my current conclusion. But I cannot really find where the problem lies.

I have also added another view (SharedViewModel, which onReceive() refers to a few times.

Any ideas?

GeofenceBroadcastReceiver

package com.example.geofenceapp.broadcastreceiver

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.example.geofenceapp.R
import com.example.geofenceapp.util.Constants.NOTIFICATION_CHANNEL_ID
import com.example.geofenceapp.util.Constants.NOTIFICATION_CHANNEL_NAME
import com.example.geofenceapp.util.Constants.NOTIFICATION_ID
import com.example.geofenceapp.util.Permissions
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.Geofence.GeofenceTransition
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.location.GeofencingEvent

class GeofenceBroadcastReceiver: BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (context == null || intent == null) {
            Log.e("GeofenceBroadcastReceiver", "Received null context or intent")
            return
        }

        val geofencingEvent = GeofencingEvent.fromIntent(intent)
        Log.e("geofencingEvent", "geofencingEvent is $geofencingEvent")
        if (geofencingEvent == null) {
            Log.e("GeofenceBroadcastReceiver", "GeofencingEvent is null. Intent details: ${intent.toUri(0)}")
            return
        }

        if (geofencingEvent.hasError()) {
            val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
            Log.e("GeofenceBroadcastReceiver", "GeofencingEvent error: $errorMessage")
            return
        }

        val geofenceTransition = geofencingEvent.geofenceTransition
        Log.d("GeofenceBroadcastReceiver", "Received geofence event: $geofenceTransition")

        // Check if the transition type is valid
        if (geofenceTransition in listOf(Geofence.GEOFENCE_TRANSITION_ENTER, Geofence.GEOFENCE_TRANSITION_EXIT, Geofence.GEOFENCE_TRANSITION_DWELL)) {
            val triggeringGeofences = geofencingEvent.triggeringGeofences
            val geofenceIds = triggeringGeofences?.joinToString(", ") { it.requestId }
            Log.d("GeofenceBroadcastReceiver", "Triggering geofences: $geofenceIds")

            // Handle specific geofence transitions
            when (geofenceTransition) {
                Geofence.GEOFENCE_TRANSITION_ENTER -> {
                    Log.d("GeofenceBroadcastReceiver", "Geofence Enter")
                    Log.d("GeofenceBroadcastReceiver", "User is within the geofence")
                    displayNotification(context, "Geofence ENTER")
                }
                Geofence.GEOFENCE_TRANSITION_EXIT -> {
                    Log.d("GeofenceBroadcastReceiver", "Geofence Exit")
                    Log.d("GeofenceBroadcastReceiver", "User exits the geofence")
                    displayNotification(context, "Geofence Exit")
                }
                Geofence.GEOFENCE_TRANSITION_DWELL -> {
                    Log.d("GeofenceBroadcastReceiver", "Geofence Dwell")
                    Log.d("GeofenceBroadcastReceiver", "User dwells within the geofence")
                    displayNotification(context, "Geofence Dwell")
                }
            }
        } else {
            Log.d("GeofenceBroadcastReceiver", "Unknown geofence transition: $geofenceTransition")
        }
    }

}

SharedViewModel

package com.example.geofenceapp.viewmodels

import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.location.GnssAntennaInfo.SphericalCorrections
import android.location.LocationManager
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.example.geofenceapp.broadcastreceiver.GeofenceBroadcastReceiver
import com.example.geofenceapp.data.DataStoreRepository
import com.example.geofenceapp.data.GeofenceEntity
import com.example.geofenceapp.data.GeofenceRepository
import com.example.geofenceapp.util.Permissions
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingRequest
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.maps.android.SphericalUtil

import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.math.sqrt

@HiltViewModel
class SharedViewModel @Inject constructor (
    application: Application,
    private val dataStoreRepository: DataStoreRepository,
    private val geofenceRepository: GeofenceRepository,
):AndroidViewModel(application) {

    val app = application

    private var geofencingClient = LocationServices.getGeofencingClient(app.applicationContext)

    var geoId:Long = 0L
    var geoName: String = "Default"
    var geoCountryCode: String = ""
    var geoLocationName: String = "Search a City"
    var geoLatLng: LatLng = LatLng(0.0, 0.0)
    var geoRadius: Float = 50f
    var geoSnapShot: Bitmap? = null

    var geoCitySelected = false
    var geofenceReady = false

    var geofencePrepared = false


    //DataStore
    val readFirstLaunch = dataStoreRepository.readFirstLaunch.asLiveData()

    fun saveFirstLaunch(firstLaunch: Boolean) =
        viewModelScope.launch(Dispatchers.IO){
            dataStoreRepository.saveFirstLaunch(firstLaunch)

        }

    //Database
    val readyGeofences = geofenceRepository.readGeofences.asLiveData()

    fun addGeofence(geofenceEntity: GeofenceEntity) =
        viewModelScope.launch(Dispatchers.IO) {
        geofenceRepository.addGeofence(geofenceEntity)
    }

    fun removeGeofence(geofenceEntity: GeofenceEntity) =
        viewModelScope.launch(Dispatchers.IO) {
            geofenceRepository.removeGeofence(geofenceEntity)
        }

    fun addGeofenceToDatabase(location:LatLng){
        val geofenceEntity =
            GeofenceEntity(
                geoId,
                geoName,
                geoLocationName,
                location.latitude,
                location.longitude,
                geoRadius,
                geoSnapShot!!,
            )
        addGeofence(geofenceEntity)
    }

    private fun setPendingIntent(geoId:Int): PendingIntent{
        val intent = Intent(app, GeofenceBroadcastReceiver::class.java)

        Log.d("setPendingIntent", "Creating PendingIntent for geoId: $geoId with action $intent")

        // Choose FLAG_IMMUTABLE or FLAG_MUTABLE based on your needs
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_MUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }

        Log.d("Flag is","flag is ${flags}")

        return PendingIntent.getBroadcast(
            app,
            geoId,
            intent,
            flags,
        ).also {
            Log.d("setPendingIntent", "PendingIntent created: $it")
        }
    }


    fun startGeofence(
        latitude: Double,
        longitude: Double,
    ){

        if(Permissions.hasBackgroundLocationPermission(app)){
            val geofence = Geofence.Builder()
                .setRequestId(geoId.toString())
                .setCircularRegion(
                    latitude,
                    longitude,
                    geoRadius,
                )
                .setExpirationDuration(Geofence.NEVER_EXPIRE)
                .setTransitionTypes(
                    Geofence.GEOFENCE_TRANSITION_ENTER
                    or Geofence.GEOFENCE_TRANSITION_EXIT
                    or Geofence.GEOFENCE_TRANSITION_DWELL
                )
                .setLoiteringDelay(5000)
                .build()
            val geofencingRequest = GeofencingRequest.Builder()
                .setInitialTrigger(
                    GeofencingRequest.INITIAL_TRIGGER_ENTER
                    or GeofencingRequest.INITIAL_TRIGGER_EXIT
                    or GeofencingRequest.INITIAL_TRIGGER_DWELL
                )
                .addGeofence(geofence)
                .build()


            geofencingClient.addGeofences(geofencingRequest, setPendingIntent(geoId.toInt())).run{
                addOnSuccessListener {
                    Log.d("Geofence","Successfully added.")
                }
                addOnFailureListener{
                    Log.d("Geofence", it.message.toString())
                }
            }
        }else{
            Log.d("Geofence","Permission not granted.")
        }
    }

    fun getBounds(center:LatLng, radius:Float): LatLngBounds{
        val distanceFromCenterToCorner = radius * sqrt(2.0)
        //to find south west corner of a point
        //we apply 225 degrees from north point
        //then apply a square root of the radius
        val southWestCorner = SphericalUtil.computeOffset(center, distanceFromCenterToCorner, 225.0)
        //to find north east corner of a point
        //we apply 45 degrees from north point
        //then apply a square root of the radius
        val northEastCorner = SphericalUtil.computeOffset(center, distanceFromCenterToCorner,45.0)

        return LatLngBounds(southWestCorner, northEastCorner)
    }

    fun checkDeviceLocationSettings(context: Context):Boolean{

        return if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            val LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            LocationManager.isLocationEnabled
        }else{
            val mode: Int = Settings.Secure.getInt(
                context.contentResolver,
                Settings.Secure.LOCATION_MODE,
                Settings.Secure.LOCATION_MODE_OFF
            )
            mode != Settings.Secure.LOCATION_MODE_OFF
        }


    }
}

Upvotes: 0

Views: 22

Answers (0)

Related Questions