I am trying to create an app that reads geofences from a text file. I have successfully worked out the file reading and the geofence creation. I can't get the geofence events, enter or exit, to get triggered. I have tested on the emulator and on a real phone with mock locations and with real gps. I am attaching what is in my manifest.xml, my broadcast receiver class and my main activity.kt. It would be great is some one can point out what I am missing. Thanks.
ON the application section of androidmanifest.xml
<receiver android:name=".GeofenceBroadcastReceiver" android:exported="true">
<intent-filter> -->
<action android:name="com.example.locationtest.ACTION_RECEIVE_GEOFENCE"/>-->
</intent-filter>-->
</receiver>
class GeofenceBroadcastReceiver : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("TAG","GRV Geofence event happened1")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = "Geofence error: ${geofencingEvent.errorCode}"
Log.d("TAG","GRV $errorMessage")
// Log or handle the error accordingly
return
}
Log.d("TAG","GRV Geofence event happened2")
// Get the transition type (ENTER, EXIT, or DWELL)
val geofenceTransition = geofencingEvent.geofenceTransition
val triggeringGeofences = geofencingEvent.triggeringGeofences
val triggeringGeofencesIds = triggeringGeofences.map { it.requestId }
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("MMdd")
val formatter2 =DateTimeFormatter.ofPattern("HH:mm:ss")
val daystamp = now.format(formatter).takeLast(4)
val timestamp = now.format(formatter2).takeLast(8)
// Check which transition type has triggered this event
when (geofenceTransition) {
Geofence.GEOFENCE_TRANSITION_ENTER -> {
triggeringGeofencesIds.forEach { geofenceId ->
val eventdesc = timestamp + "#" + geofenceId + "#" + "ENTR"
if (context != null) {
writeToExternalStorageFile(context, eventdesc, daystamp + "geofenceEvents.txt" )
}
Log.d("TAG","GRV Geofence with ID $geofenceId triggered a transition.")
}
}
Geofence.GEOFENCE_TRANSITION_EXIT -> {
triggeringGeofencesIds.forEach { geofenceId ->
val eventdesc = timestamp + "#" + geofenceId + "#" + "EXIT"
if (context != null) {
writeToExternalStorageFile(context, eventdesc, daystamp + "geofenceEvents.txt" )
}
Log.d("TAG","GRV Geofence with ID $geofenceId triggered a transition.")
}
}
/*Geofence.GEOFENCE_TRANSITION_DWELL -> {
// Handle geofence dwell (if you've set this transition type)
}
else -> {
// Log or handle the error accordingly
}*/
}
// This is where you would start an activity, send a notification, etc.
// For example, to send a notification:
// sendNotification(context, "Geofence Transition Detected", "You have entered/exited a geofence.")
}
}
class MainActivity : ComponentActivity() {
private val LOCATION_PERMISSION_REQUEST_CODE = 1
lateinit var geofencingClient: GeofencingClient
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("TAG","GRV Entro")
geofencingClient = LocationServices.getGeofencingClient(this)
checkAndRequestPermissions()
setContentView(R.layout.activity_main)
Log.d("TAG","GRV Entro1")
removeAllGeofences(this)
val geofences = readFileLinesToArray(this, "GeofenceList.txt")
if (geofences != null) {
Log.d("TAG","GRV Leyendo geocercas")
processGeofenceData(this, geofences, 100.00F)
}
val editText = findViewById<EditText>(R.id.editTextInput)
val button = findViewById<Button>(R.id.buttonSubmit)
val textViewResult = findViewById<TextView>(R.id.textViewResult)
val toggleButton = findViewById<ToggleButton>(R.id.toggleButton)
// Button Click Listener
button.setOnClickListener {
val inputText = editText.text.toString()
val funciono = generateAndWriteLocationInfo(this, inputText)
removeAllGeofences(this)
val geofences = readFileLinesToArray(this, "GeofenceList.txt")
if (geofences != null) {
processGeofenceData(this, geofences, 100.00F)
}
textViewResult.text = "Result: ${funciono.toString()}"
Toast.makeText(this, "Input: ${funciono.toString()}", Toast.LENGTH_SHORT).show()
}
// Toggle Button Listener
toggleButton.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
// The toggle is enabled
Toast.makeText(this, "Toggle On", Toast.LENGTH_SHORT).show()
} else {
// The toggle is disabled
Toast.makeText(this, "Toggle Off", Toast.LENGTH_SHORT).show()
}
}
}
private fun checkAndRequestPermissions() {
Log.d("TAG","Entra a permisos")
val fineLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
val backgroundLocationPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
PackageManager.PERMISSION_GRANTED // Background location isn't needed/requested before Android 10 (Q)
}
val listPermissionsNeeded = mutableListOf<String>()
if (fineLocationPermission != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (backgroundLocationPermission != PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Note: Background location permission should be requested separately and only after fine location permission has been granted.
listPermissionsNeeded.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
if (listPermissionsNeeded.isNotEmpty()) {
ActivityCompat.requestPermissions(this, listPermissionsNeeded.toTypedArray(), LOCATION_PERMISSION_REQUEST_CODE)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
LOCATION_PERMISSION_REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("TAG","Permiso proporcionado")
// Permission was granted. You can now access the location.
} else {
Log.d("TAG","Permiso negado")
// Permission denied. You can notify the user and disable features that require the permission.
}
return
}
}
}
fun processGeofenceData(context: Context, geofenceData: Array<String>, radius: Float) {
geofenceData.forEach { data ->
val parts = data.split("#")
Log.d("TAG","Procesando Geocercas")
if (parts.size >= 4) {
val geofenceId = parts[0]
// Assuming the address is not used directly for geofence creation here
val latitude = parts[2].toDoubleOrNull()
val longitude = parts[3].toDoubleOrNull()
if (latitude != null && longitude != null) {
createAndActivateGeofence(context, geofenceId, latitude, longitude, radius)
} else {
Log.d("TAG","GRV Invalid latitude or longitude for $geofenceId")
}
}
}
}
private val geofencePendingIntent: PendingIntent by lazy {
Log.d("TAG","GRV pendingIntent")
val intent = Intent(this, GeofenceBroadcastReceiver::class.java).apply {
action = "com.example.locationtest.ACTION_RECEIVE_GEOFENCE"}
Log.d("TAG","GRV pendingIntent2")
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
fun createAndActivateGeofence(context: Context, geofenceId: String, latitude: Double, longitude: Double, radius: Float) {
//val geofencingClient: GeofencingClient = LocationServices.getGeofencingClient(context)
Log.d("TAG","GRV Armando Geocercas")
val geofence = Geofence.Builder()
.setRequestId(geofenceId)
.setCircularRegion(latitude, longitude, radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofence(geofence)
}.build()
// val intent = Intent(context, GeofenceBroadcastReceiver::class.java).apply {
// action = "com.example.locationtest.ACTION_RECEIVE_GEOFENCE"
// }
// val geofencePendingIntent: PendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
// Corrected permission check
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent).run {
addOnSuccessListener {
// Handle success
Log.d("TAG","GRV Geofence Added: $geofenceId")
}
addOnFailureListener {
// Handle failure
Log.d("TAG","GRV Failed to add geofence: $geofenceId")
}
}
} else {
Log.d("TAG","GRV Location permission not granted")
// Here you should actually request the permissions if not already granted,
// not just print a message. This could be a call to requestPermissions
// if within an Activity, or a signal to the user to enable permissions via app settings.
}
}
fun removeAllGeofences(context: Context) {
//val geofencingClient: GeofencingClient = LocationServices.getGeofencingClient(context)
// The PendingIntent that was used to add the geofences
val intent = Intent(context, GeofenceBroadcastReceiver::class.java).apply {
action = "com.example.locationtest.ACTION_RECEIVE_GEOFENCE" // Use the same action as when adding the geofences
}
val geofencePendingIntent: PendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
// Check for location permissions for completeness, though it's not strictly necessary for removal
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
geofencingClient.removeGeofences(geofencePendingIntent).run {
addOnSuccessListener {
// Handle success
Log.d("TAG","GRV All geofences successfully removed.")
}
addOnFailureListener {
// Handle failure
Log.d("TAG","GRV Failed to remove geofences.")
}
}
} else {
Log.d("TAG","GRV Location permission not granted")
// Handle lack of permission (e.g., request permissions here)
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
LocationTestTheme {
Greeting("Android")
}
}