diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 488604f..956d6fd 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -7,11 +7,11 @@
-
+
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index a5bcd75..f1cf180 100644
Binary files a/README.md and b/README.md differ
diff --git a/app/build.gradle b/app/build.gradle
index 85ca93e..bf52d70 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -34,6 +34,7 @@ android {
buildFeatures {
viewBinding true
}
+ namespace 'com.ar.application'
}
dependencies {
@@ -43,7 +44,9 @@ dependencies {
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.gms:play-services-maps:18.1.0'
+ implementation 'com.google.android.gms:play-services-location:20.0.0'
implementation 'com.google.firebase:firebase-messaging-ktx:23.0.8'
+ implementation 'com.google.firebase:firebase-database-ktx:20.0.6'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f39a1b2..bf56e08 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,12 +1,13 @@
+ xmlns:tools="http://schemas.android.com/tools">
-
-
-
-
+
+
+
+
+
+
+ android:value="AIzaSyBesYoZOX1GYDg1n5JySelTKaTPafyfm1w" />
+
\ No newline at end of file
diff --git a/app/src/main/java/com/ar/application/Fence.kt b/app/src/main/java/com/ar/application/Fence.kt
new file mode 100644
index 0000000..eb11fdc
--- /dev/null
+++ b/app/src/main/java/com/ar/application/Fence.kt
@@ -0,0 +1,7 @@
+package com.ar.application
+
+data class Fence(
+ var key: String = "",
+ var lat: Double = 0.0,
+ var lng: Double = 0.0,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ar/application/GeofenceReceiver.kt b/app/src/main/java/com/ar/application/GeofenceReceiver.kt
new file mode 100644
index 0000000..3834420
--- /dev/null
+++ b/app/src/main/java/com/ar/application/GeofenceReceiver.kt
@@ -0,0 +1,54 @@
+package com.ar.application
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.ar.application.Geofencing.Companion.showNotification
+import com.google.android.gms.location.Geofence
+import com.google.android.gms.location.GeofencingEvent
+import com.google.firebase.database.DataSnapshot
+import com.google.firebase.database.DatabaseError
+import com.google.firebase.database.ValueEventListener
+import com.google.firebase.database.ktx.database
+import com.google.firebase.database.ktx.getValue
+import com.google.firebase.ktx.Firebase
+
+class GeofenceReceiver: BroadcastReceiver() {
+ lateinit var key: String
+ lateinit var message: String
+ override fun onReceive(p0: Context?, p1: Intent?) {
+ if (p0 != null) {
+ val geofencingEvent = p1?.let { GeofencingEvent.fromIntent(it) }
+ val geofenceTransition = geofencingEvent?.geofenceTransition
+
+ if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER
+ || geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
+ if (p1 != null) {
+ key = p1.getStringExtra("key")!!
+ message = p1.getStringExtra("message")!!
+ }
+
+ // retrieving data from database
+ val database = Firebase.database
+ val reference = database.getReference("fences")
+ val fenceListener = object : ValueEventListener {
+ override fun onDataChange(snapshot: DataSnapshot) {
+ val fence = snapshot.getValue()
+ if (fence != null) {
+ showNotification(
+ p0.applicationContext,
+ message
+ )
+ }
+ }
+
+ override fun onCancelled(error: DatabaseError) {
+ println("Fence: onCancelled: ${error.details}")
+ }
+ }
+ val child = reference.child(key)
+ child.addValueEventListener(fenceListener)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/ar/application/Geofencing.kt b/app/src/main/java/com/ar/application/Geofencing.kt
index 35bca36..3e6d3fc 100644
--- a/app/src/main/java/com/ar/application/Geofencing.kt
+++ b/app/src/main/java/com/ar/application/Geofencing.kt
@@ -1,21 +1,54 @@
package com.ar.application
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Color
+import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
+import androidx.annotation.RequiresPermission
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
-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.ar.application.databinding.ActivityGeofencingBinding
+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.LocationServices
+import com.google.android.gms.maps.CameraUpdateFactory
+import com.google.android.gms.maps.model.CircleOptions
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
-import com.ar.application.databinding.ActivityGeofencingBinding
+import com.google.firebase.database.ktx.database
+import com.google.firebase.ktx.Firebase
+import kotlin.random.Random
class Geofencing : AppCompatActivity(), OnMapReadyCallback {
- private lateinit var mMap: GoogleMap
+ private val LOCATION_REQUEST_CODE = 123
+ private val GEOFENCE_REQUEST_CODE = 456 // only for android version >= 10
+ private val CAMERA_ZOOM_LEVEL = 18f
+ private val GEOFENCE_RADIUS = 500 // change later
+ private val GEOFENCE_ID = "REMINDER_GEOFENCE_ID"
+ private val GEOFENCE_EXPIRATION = 10 * 24 * 60 * 60 * 1000 // 10 days, change later
+ private val GEOFENCE_DWELL_DELAY = 10 * 1000 // 10 secs, change later
+ private val TAG: String = Geofencing::class.java.simpleName
+
+ private lateinit var map: GoogleMap
private lateinit var binding: ActivityGeofencingBinding
+ private lateinit var fusedLocationClient: FusedLocationProviderClient
+ private lateinit var geofencingClient: GeofencingClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -28,7 +61,8 @@ class Geofencing : AppCompatActivity(), OnMapReadyCallback {
.findFragmentById(R.id.mapGeofence) as SupportMapFragment
mapFragment.getMapAsync(this)
-
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
+ geofencingClient = LocationServices.getGeofencingClient(this)
}
/**
@@ -40,12 +74,264 @@ class Geofencing : AppCompatActivity(), OnMapReadyCallback {
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
+ //@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
override fun onMapReady(googleMap: GoogleMap) {
- mMap = googleMap
+ map = googleMap
+ map.uiSettings.isZoomControlsEnabled = true
+
+ if (!areLocationPermsGranted()) {
+ askForLocationPerms()
+ }
+ else {
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED &&
+ ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED) {
+// askForLocationPerms()
+ }
+ this.map.isMyLocationEnabled = true
+
+ // getting last known location data
+ fusedLocationClient.lastLocation.addOnSuccessListener {
+ if (it != null) {
+ with (map) {
+ val latLng = LatLng(it.latitude, it.longitude)
+ addMarker(MarkerOptions().position(latLng))
+ moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, CAMERA_ZOOM_LEVEL))
+ }
+ }
+ else {
+ with (map) {
+ addMarker(MarkerOptions().position(LatLng(17.429, 78.455)))
+ moveCamera(CameraUpdateFactory.newLatLngZoom(
+ LatLng(17.429, 78.455), CAMERA_ZOOM_LEVEL)
+ // Panjagutta coordinates
+ )
+ }
+ TODO("Manually setting the last known location for testing," +
+ "but that doesn't make sense." +
+ "So, think of something else.")
+ }
+ }
+ }
+ setLongClick(map)
+ setPoiClick(map)
+ }
+
+ private fun areLocationPermsGranted(): Boolean { // checks if location permissions have been granted
+ return ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED ||
+ ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+
+ private fun askForLocationPerms() { // requests for location permissions
+ val permissions = mutableListOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ permissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ }
+
+ ActivityCompat.requestPermissions(
+ this,
+ permissions.toTypedArray(),
+ LOCATION_REQUEST_CODE
+ )
+ }
+
+ private fun askForBackgroundLocationPerm() { // requests for only background location
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
+ GEOFENCE_REQUEST_CODE
+ )
+ }
+ }
+
+ private fun setPoiClick(map: GoogleMap) {
+ map.setOnPoiClickListener { poi ->
+ map.addMarker(
+ MarkerOptions()
+ .position(poi.latLng)
+ .title(poi.name)
+ )?.showInfoWindow()
+
+ //scheduleJob()
+ }
+ }
+
+ private fun setLongClick(map: GoogleMap) {
+ map.setOnMapLongClickListener { latlng ->
+ map.addMarker(
+ MarkerOptions().position(latlng)
+ .title("Geofence centre")
+ )?.showInfoWindow()
+ map.addCircle(
+ CircleOptions()
+ .center(latlng)
+ .strokeColor(Color.argb(50, 70, 70, 70))
+ .fillColor(Color.argb(70, 150, 150, 150))
+ .radius(GEOFENCE_RADIUS.toDouble())
+ )
+ map.moveCamera(CameraUpdateFactory.newLatLngZoom(latlng, CAMERA_ZOOM_LEVEL))
+
+ val database = Firebase.database
+ val reference = database.getReference("fences")
+ val key = reference.push().key
+ if (key != null) {
+ val reminder = Fence(key, latlng.latitude, latlng.longitude)
+ reference.child(key).setValue(reminder)
+ }
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_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.
+ }
+ createGeofence(latlng, key!!, geofencingClient)
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ private fun createGeofence(location: LatLng, key: String, geofencingClient: GeofencingClient) {
+ val geofence = Geofence.Builder()
+ .setRequestId(GEOFENCE_ID)
+ .setCircularRegion(location.latitude, location.longitude, GEOFENCE_RADIUS.toFloat())
+ .setExpirationDuration(GEOFENCE_EXPIRATION.toLong())
+ // defines the lifetime of the geofence
+ .setTransitionTypes(
+ Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_DWELL
+ )
+ // transitions of the user when actions are to be triggered
+ .setLoiteringDelay(GEOFENCE_DWELL_DELAY)
+ // min duration of user in geofence for action to be triggered
+ .build()
+
+ val geofenceRequest = GeofencingRequest.Builder()
+ .setInitialTrigger(Geofence.GEOFENCE_TRANSITION_ENTER)
+ .addGeofence(geofence)
+ .build()
+
+ val intent = Intent(this, GeofenceReceiver::class.java)
+ .putExtra("key", key)
+ .putExtra("message", "Geofence detected!")
+
+ val pendingIntent = PendingIntent.getBroadcast(
+ applicationContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ if (ContextCompat.checkSelfPermission(
+ applicationContext,
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED) {
+ askForBackgroundLocationPerm()
+ }
+ else {
+ geofencingClient.addGeofences(geofenceRequest, pendingIntent)
+ }
+ }
+ else {
+ geofencingClient.addGeofences(geofenceRequest, pendingIntent)
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == GEOFENCE_REQUEST_CODE) {
+ if (permissions.isNotEmpty() && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(
+ this,
+ "Please grant background location permission!",
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ askForBackgroundLocationPerm()
+ }
+ }
+ else if (requestCode == LOCATION_REQUEST_CODE) {
+ if (permissions.isNotEmpty() &&
+ (grantResults[0] == PackageManager.PERMISSION_GRANTED
+ || grantResults[1] == PackageManager.PERMISSION_GRANTED)) {
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED &&
+ ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED) {
+ askForLocationPerms()
+ }
+ map.isMyLocationEnabled = true
+ onMapReady(map)
+ }
+ else {
+ Toast.makeText(
+ this,
+ "Please grant location permissions!",
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ askForLocationPerms()
+ }
+ }
+ }
+
+ companion object {
+ fun showNotification(context: Context, message: String) {
+ val channelID = "FENCE_NOTIFICATION_CHANNEL"
+ var notifID = 420
+ notifID += Random(notifID).nextInt(1, 69)
+
+ val notifBuilder = NotificationCompat.Builder(context.applicationContext, channelID)
+ .setSmallIcon(R.drawable.ic_fence_detected)
+ .setContentTitle(context.getString(R.string.app_name))
+ .setContentText(message)
+ .setStyle(
+ NotificationCompat.BigTextStyle().bigText(message)
+ )
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+
+ val notifManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
+ as NotificationManager
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ channelID,
+ context.getString(R.string.app_name),
+ NotificationManager.IMPORTANCE_DEFAULT
+ ).apply { description = context.getString(R.string.app_name) }
- // Add a marker in Sydney and move the camera
- val sydney = LatLng(-34.0, 151.0)
- mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
- mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
+ notifManager.createNotificationChannel(channel)
+ }
+ notifManager.notify(notifID, notifBuilder.build())
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_fence_detected.xml b/app/src/main/res/drawable/ic_fence_detected.xml
new file mode 100644
index 0000000..11fdbe5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fence_detected.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cd40ee5..16a2a29 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,4 +6,5 @@
Go to Trace Route Page
Go to Geo-fencing
SOS
+ MapsActivity
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 7ce7085..6581c07 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,11 +1,11 @@
buildscript {
dependencies {
- classpath 'com.google.gms:google-services:4.3.13'
+ classpath 'com.google.gms:google-services:4.3.14'
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '7.2.2' apply false
- id 'com.android.library' version '7.2.2' apply false
+ id 'com.android.application' version '7.3.0' apply false
+ id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' apply false
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2df3794..aea147c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Sep 09 10:29:15 IST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME