Reputation: 11
Android.Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Geo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<service
android:name=".MapsActivity"
android:foregroundServiceType="location" /> <!-- Any inner elements would go here. -->
<!--
Before you run your application, you need a Google Maps API key.
To get one, follow the directions here:
https://developers.google.com/maps/documentation/android-sdk/get-api-key
Once you have your API key (it starts with "AIza"), define a new property in your
project's local.properties file (e.g. MAPS_API_KEY=Aiza...), and replace the
"YOUR_API_KEY" string in this file with "${MAPS_API_KEY}".
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name=".MapsActivity"
android:exported="true"
android:label="@string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".GeofenceBroadcastReceiver"
android:exported="true"
android:permission="TODO">
<intent-filter>
<action android:name="com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION" />
</intent-filter>
</receiver>
</application>
</manifest>
MapsActivity
package com.example.geo
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.annotation.RequiresApi
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import com.example.geo.databinding.ActivityMapsBinding
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.location.Location
import android.os.Looper
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.common.internal.Constants
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingClient
import com.google.android.gms.location.GeofencingRequest
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.LocationServices
import com.google.android.gms.maps.model.CircleOptions
import com.google.android.gms.maps.model.Marker
const val CHANNEL_ID = "GeoChannel"
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var geofencingClient: GeofencingClient
private val REQUEST_CODE = 0
private val geo_Id = "MyGeofence"
private val entry_latitude = 26.267775
private val entry_longitude = 81.505404
private val geoRad = 282F
private val expiration_time = 10000000000000
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private var permissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.POST_NOTIFICATIONS
)
private var geofencelist : MutableList<Geofence> = mutableListOf()
@RequiresApi(Build.VERSION_CODES.Q)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
//Notification channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel.
val name = "Work"
val descriptionText = "geofencing"
val importance = NotificationManager.IMPORTANCE_HIGH
val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
mChannel.description = descriptionText
// Register the channel with the system. You can't change the importance
// or other notification behaviors after this.
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(mChannel)
//mChannel.
}
val textTitle = "You hae checked in "
val textContent = "dggdgfdf"
// Create an explicit intent for an Activity in your app.
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
// val builder = NotificationCompat.Builder(this, CHANNEL_ID)
// .setSmallIcon(R.drawable.notification_icon)
// .setContentTitle("My notification")
// .setContentText("Hello World!")
// .setPriority(NotificationCompat.PRIORITY_DEFAULT)
// // Set the intent that fires when the user taps the notification.
// .setContentIntent(pendingIntent)
// .setAutoCancel(true)
//Location
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
//Location permission
when {
permissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
} -> {
// Fine and coarse location permissions granted
}
permissions.any {
ActivityCompat.shouldShowRequestPermissionRationale(this, it)
} -> {
// Show rationale for fine/coarse location
// showInContextUI(...)
}
else -> {
// Request fine/coarse location permissions
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE)
}
}
//For background location
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// // Check for background location on API 29+
// ActivityCompat.requestPermissions(
// this,
// arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
// REQUEST_CODE
// )
// }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
// All permissions granted, perform action
// performAction(...)
}
// ActivityCompat.shouldShowRequestPermissionRationale(
// this,
// Manifest.permission.ACCESS_BACKGROUND_LOCATION
// ) -> {
// // Show rationale for background location
// //showInContextUI(...)
else {// Request background location permission
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
REQUEST_CODE
)
}
}
//Geofencing
geofencingClient = LocationServices.getGeofencingClient(this)
geofencelist.add(Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(geo_Id)
// Set the circular region of this geofence.
.setCircularRegion(
entry_latitude,
entry_longitude,
geoRad
)
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
.setExpirationDuration(expiration_time)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT )
.setLoiteringDelay(10000)
// Create the geofence.
.build())
val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
// intent.action = ACTION_GEOFENCE_EVENT
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
PendingIntent.getBroadcast(this, 0, intent, flags)
}
geofencingClient.addGeofences(getGeofencingRequest(), geofencePendingIntent).run {
addOnSuccessListener {
// Geofences added
val fence = LatLng(entry_latitude, entry_longitude)
mMap.addMarker(MarkerOptions().position(fence).title("Marker in center"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(fence))
val geofenceCircle = mMap.addCircle(
CircleOptions()
.center(LatLng(entry_latitude, entry_longitude))
.radius(geoRad.toDouble())
.strokeColor(Color.GREEN)
.fillColor(Color.argb(64, 0, 125, 0))
)
}
addOnFailureListener {
// Failed to add geofences
// ...
}
}
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
enableMyLocation()
// Live location
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
for (location in locationResult.locations) {
val cur = LatLng(location.latitude, location.longitude)
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(cur,20f))
}
}
}
startLocationUpdates()
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_CODE -> {
// If request is cancelled, the result arrays are empty.
if ((grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// Permission is granted. Continue the action or workflow
// in your app.
} else {
// Explain to the user that the feature is unavailable because
// the feature requires a permission that the user has denied.
// At the same time, respect the user's decision. Don't link to
// system settings in an effort to convince the user to change
// their decision.
}
return
}
// Add other 'when' lines to check for other
// permissions this app might request.
else -> {
// Ignore all other requests.
}
}
}
private fun getGeofencingRequest(): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofencelist)
}.build()
}
private fun startLocationUpdates() {
// ... request location permissions ...
val locationRequest = LocationRequest.create().apply {
interval = 100// Update interval in milliseconds
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}
private fun enableMyLocation() {
// Check if location permissions are granted
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
// Enable the blue dot on the map if permissions are granted
mMap.isMyLocationEnabled = true
} else {
// Request location permissions if not granted
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE)
}
}
companion object {
internal const val ACTION_GEOFENCE_EVENT =
"com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION"
}
}
const val TAGED = "TAGED"
GeofenceBroadcastReceiver
package com.example.geo
import android.content.BroadcastReceiver
import android.content.ContentValues.TAG
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.location.GeofencingEvent
import android.Manifest
import androidx.core.app.NotificationCompat
import com.example.geo.MapsActivity.Companion.ACTION_GEOFENCE_EVENT
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAGED, "GeofenceBroadcastReceiver triggered")
// if (intent != null) {
// if (intent.action == ACTION_GEOFENCE_EVENT) {
val geofencingEvent = intent?.let { GeofencingEvent.fromIntent(it) }
if (geofencingEvent != null && geofencingEvent.hasError()) {
val errorMessage =
GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAGED, "Geofencing error: $errorMessage")
return
}
val geofenceTransition = geofencingEvent?.geofenceTransition
Log.d(TAGED, "Geofence transition: $geofenceTransition")
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ) {
// Get the geofences that were triggered. A single event can trigger multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Get the transition details as a String.
val geofenceTransitionDetails = triggeringGeofences?.let {
getGeofenceTransitionDetails(
context!!,
geofenceTransition,
it
)
}
// Send notification and log the transition details.
if (geofenceTransitionDetails != null) {
sendNotification(context!!, geofenceTransitionDetails)
}
if (geofenceTransitionDetails != null) {
Log.i(TAGED, geofenceTransitionDetails)
}
} else {
Log.e(TAGED, "Geofence transition not handled: $geofenceTransition")
}
// }
// }
}
private fun sendNotification(context:Context, message: String?) {
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon) // Replace with your notification icon
.setContentTitle("Geofence Alert")
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
val notificationManager = NotificationManagerCompat.from(context)
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// Consider requesting the missing permission here
return
}
val notificationId = 123 // You can use a unique ID for each notification
notificationManager.notify(notificationId, builder.build())
}
private fun getGeofenceTransitionDetails(
context: Context,geofenceTransition: Int,
triggeringGeofences: List<Geofence>
): String {
val geofenceTransitionString = when (geofenceTransition) {
Geofence.GEOFENCE_TRANSITION_ENTER -> "Entered"
Geofence.GEOFENCE_TRANSITION_EXIT -> "Exited"
Geofence.GEOFENCE_TRANSITION_DWELL -> "Dwell"
else -> "Unknown Transition"
}
// Get the Ids of each geofence that was triggered.
val triggeringGeofencesIdsList = mutableListOf<String>()
for (geofence in triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.requestId)
}
val triggeringGeofencesIdsString = triggeringGeofencesIdsList.joinToString(", ")
return "Geofence transition: $geofenceTransitionString - $triggeringGeofencesIdsString"
}
}
I tried the get the log message and I am getting these messages
2024-08-25 23:30:36.829 13719-13719 TAGED com.example.geo D GeofenceBroadcastReceiver triggered 2024-08-25 23:30:36.829 13719-13719 TAGED com.example.geo D Geofence transition: null 2024-08-25 23:30:36.829 13719-13719 TAGED com.example.geo E Geofence transition not handled: null why I
Upvotes: 1
Views: 63
Reputation: 97
1. Check the Intent Action
<receiver
android:name=".GeofenceBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION" />
</intent-filter>
</receiver>
In your GeofenceBroadcastReceiver, check for this :
if (intent?.action == "com.google.android.gms.location.ACTION_GEOFENCE_TRANSITION") {
// Your geofence processing code here
}
2. Check for GeofencingEvent Errors
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAGED, "Geofencing error: $errorMessage")
return
}
Upvotes: 0