From 2a5799b711a64538cbb091c22348e70b22ff2d83 Mon Sep 17 00:00:00 2001 From: 14Sandee Date: Fri, 22 Dec 2023 16:42:20 +0530 Subject: [PATCH] geofence support --- app/src/main/AndroidManifest.xml | 7 + .../apputils/BootCompleteReceiver.java | 19 +-- .../apputils/NotificationService.java | 7 +- .../DefaultLocationClient.java | 3 +- .../locationupdates/LocationService.java | 136 +++++++++++++++--- .../patient_dashboard/DashBoardActivity.java | 89 ++++++------ .../PatientMainViewModel.java | 2 - .../GeoFenceBroadcastReceiver.java | 31 ++-- .../patientgeofencing/GeoFenceHelper.java | 1 - 9 files changed, 187 insertions(+), 108 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c2bddd..12c4891 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -239,6 +240,12 @@ + + + + + = 0){ AppUtil.updateSeniorOutOfGeofence(iNotificationReceivedEvent.getContext(), false); - AppUtil.updatePatientGeofence(iNotificationReceivedEvent.getContext(), - lat+"", lng+"", - radius+"", "kms", message); + Intent geofenceIntent = new Intent(iNotificationReceivedEvent.getContext(), LocationService.class); + geofenceIntent.setAction(LocationService.ACTION_START_GEOFENCE); + ContextCompat.startForegroundService(iNotificationReceivedEvent.getContext(), geofenceIntent); } }catch (Exception e){ Log.e(GEOFENCE_TAG, "COULDN'T UPDATE GEOFENCE DETAILS FROM THE NOTIFICATION: " + e); diff --git a/app/src/main/java/com/app/simplitend/locationupdates/DefaultLocationClient.java b/app/src/main/java/com/app/simplitend/locationupdates/DefaultLocationClient.java index 27efa6b..65754e3 100644 --- a/app/src/main/java/com/app/simplitend/locationupdates/DefaultLocationClient.java +++ b/app/src/main/java/com/app/simplitend/locationupdates/DefaultLocationClient.java @@ -52,7 +52,8 @@ public class DefaultLocationClient implements LocationClient{ } locationRequest = new LocationRequest.Builder(PRIORITY_HIGH_ACCURACY, interval) - .setMinUpdateDistanceMeters(50) // receive updates only if user has moved over 100 meters + .setMinUpdateDistanceMeters(100) // receive updates only if user has moved over 100 meters + .setMinUpdateIntervalMillis(10_000) // fastest update to receive every 10 secs .setPriority(PRIORITY_HIGH_ACCURACY) .build(); diff --git a/app/src/main/java/com/app/simplitend/locationupdates/LocationService.java b/app/src/main/java/com/app/simplitend/locationupdates/LocationService.java index f08a024..436ca29 100644 --- a/app/src/main/java/com/app/simplitend/locationupdates/LocationService.java +++ b/app/src/main/java/com/app/simplitend/locationupdates/LocationService.java @@ -1,17 +1,21 @@ package com.app.simplitend.locationupdates; import static android.content.Intent.ACTION_BATTERY_CHANGED; +import static com.app.simplitend.patientgeofencing.GeoFenceHelper.GEOFENCE_ID; import static com.app.simplitend.patientgeofencing.GeoFenceHelper.GEOFENCE_TAG; import static com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver.LOCATION_EXTRA_KEY; import static com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver.LOCATION_REQUEST_TAG; +import android.Manifest; import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.location.Location; import android.os.BatteryManager; import android.os.IBinder; @@ -19,17 +23,29 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import com.app.simplitend.BuildConfig; import com.app.simplitend.R; import com.app.simplitend.apputils.AppUtil; +import com.app.simplitend.apputils.PatientDataCache; import com.app.simplitend.apputils.RetrofitHelper; +import com.app.simplitend.caregiverdashboard.mvvm.CgHomeContracts; +import com.app.simplitend.caregiverdashboard.mvvm.CgHomeRepository; import com.app.simplitend.caregiverdashboard.mvvm.NotificationApiService; +import com.app.simplitend.caregiverdashboard.mvvm.models.GeoFenceDetails; import com.app.simplitend.chats.SocketHelper; +import com.app.simplitend.patientgeofencing.GeoFenceHelper; import com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver; import com.app.simplitend.welcome.welcomepatient.mvvm.models.CallResponse; +import com.app.simplitend.welcome.welcomepatient.mvvm.models.PatientData; +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.model.LatLng; import java.util.HashMap; import java.util.Locale; @@ -42,15 +58,16 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public class LocationService extends Service implements LocationClient.DefaultLocationUpdates { +public class LocationService extends Service implements LocationClient.DefaultLocationUpdates, CgHomeContracts.GetGeoFenceCallback { public static final String ACTION_START_LOCATION_UPDATES = "com.simplitent.action_start_lu"; public static final String ACTION_STOP_LOCATION_UPDATES = "com.simplitent.action.stop_lu"; + public static final String ACTION_START_GEOFENCE = "com.simplitent.action.start_geofence"; public static final String LOCATION_NOTIFICATION_CHANNEL_ID = "location"; public static final String LOCATION_UPDATE_MIN_INTERVAL = "min_interval"; - public static final int LOCATION_INTERVAL_BASE_TIME = 25 * 1000; + public static final int LOCATION_INTERVAL_BASE_TIME = 2 * 60 * 1000; private static final int LOCATION_UPDATES_NOTIFICATION_ID = 112; @@ -62,6 +79,18 @@ public class LocationService extends Service implements LocationClient.DefaultLo public void onCreate() { super.onCreate(); Log.d(LOCATION_REQUEST_TAG, "onCreate: SERVICE STARTED"); + + Notification notification = new NotificationCompat.Builder(this, LOCATION_NOTIFICATION_CHANNEL_ID) + .setContentTitle("Your phone is connected to your caregiver") + .setSmallIcon(R.mipmap.ic_launcher_round) + .setPriority(Notification.PRIORITY_HIGH) + .setOngoing(true) + .build(); + + NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + startForeground(LOCATION_UPDATES_NOTIFICATION_ID, notification); + notificationManager.notify(LOCATION_UPDATES_NOTIFICATION_ID, notification); + locationClient = new DefaultLocationClient(this, LocationServices.getFusedLocationProviderClient(this)); @@ -91,8 +120,11 @@ public class LocationService extends Service implements LocationClient.DefaultLo if (intent != null && intent.getAction() != null) { switch (intent.getAction()) { case ACTION_START_LOCATION_UPDATES: - removeLocationUpdates(); - startLocationUpdates(10_000); // 10 seconds interval + int minInterval = intent.getIntExtra(LOCATION_UPDATE_MIN_INTERVAL, 0); + if (minInterval != 0){ + removeLocationUpdates(); + startLocationUpdates(minInterval); + } break; case ACTION_STOP_LOCATION_UPDATES: stopLocationUpdates(); @@ -100,6 +132,9 @@ public class LocationService extends Service implements LocationClient.DefaultLo // setting that senior is at home for start AppUtil.updateSeniorOutOfGeofence(this, false); break; + case ACTION_START_GEOFENCE: + setGeofence(); + break; } } @@ -116,23 +151,11 @@ public class LocationService extends Service implements LocationClient.DefaultLo private void startLocationUpdates(int minInterval) { SocketHelper.getInstance().establishConnection(null); - Notification notification = new NotificationCompat.Builder(this, LOCATION_NOTIFICATION_CHANNEL_ID) - .setContentTitle("Your phone is connected to your caregiver") - .setSmallIcon(R.mipmap.ic_launcher_round) - .setPriority(Notification.PRIORITY_HIGH) - .setOngoing(true) - .build(); - - NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - try { locationClient.getLocationUpdates(minInterval, this); } catch (LocationClient.LocationException e) { // do nothing } - - startForeground(LOCATION_UPDATES_NOTIFICATION_ID, notification); - notificationManager.notify(LOCATION_UPDATES_NOTIFICATION_ID, notification); } @Override @@ -324,4 +347,85 @@ public class LocationService extends Service implements LocationClient.DefaultLo } }); } + + // geofence functions + private void setGeofence() { + // retrieving geofence + CgHomeRepository.getHomeRepository().getGeoFenceDetails(AppUtil.getPatientUid(this) + "", + "", "Bearer " + AppUtil.getPatientToken(this), + this); + } + + public void validateAndAddGeofence(GeoFenceDetails geoFenceDetails) { + if (geoFenceDetails.radius != null && geoFenceDetails.type != null) { + PatientDataCache.setPatientData(null); // for fresh data + PatientDataCache.getPatientData(this, (patientData -> { + if (patientData != null) { + if (AppUtil.shouldAddPatientGeofence(this, + geoFenceDetails.radius, geoFenceDetails.type, patientData)) { + // should add a geofence + setGeofence(this, geoFenceDetails, patientData); + } else { + Log.d(GEOFENCE_TAG, "onGeofenceDetailsFetched: should not add patient geofence because GEOFENCE DETAILS: " + geoFenceDetails + " PATIENT DETAILS: " + patientData); + } + } + }), false); + } + + } + + public void setGeofence(Context activity, GeoFenceDetails geoFenceDetails, PatientData patientData) { + LatLng latLng; + float radius; + try { + latLng = new LatLng(Double.parseDouble(patientData.lat), Double.parseDouble(patientData.lng)); + radius = Float.parseFloat(geoFenceDetails.radius); + } catch (Exception e) { + latLng = null; + radius = 0; + } + + if (latLng != null && radius != 0) { + if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + Log.d(GEOFENCE_TAG, "setGeofence: NO FINE LOCATION PERMISSION" ); + return; + } + + addGeoFence(latLng, radius, activity, geoFenceDetails.type, geoFenceDetails.message); + } + } + + @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) + private void addGeoFence(@NonNull LatLng latLng, float GEOFENCING_RADIUS , Context activity, String unit, String message) { + AppUtil.removeGeofence(activity); + + GeoFenceHelper geoFenceHelper = new GeoFenceHelper(activity); + GeofencingClient geofencingClient = LocationServices.getGeofencingClient(activity); + + Geofence geofence = geoFenceHelper.getGeoFence(GEOFENCE_ID, latLng, GEOFENCING_RADIUS, + Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT); + GeofencingRequest geofencingRequest = geoFenceHelper.getGeoFencingRequest(geofence); + PendingIntent pendingIntent = geoFenceHelper.getPendingIntent(); + + geofencingClient.addGeofences(geofencingRequest, pendingIntent) + .addOnSuccessListener(aVoid -> { + Log.d(GEOFENCE_TAG, "Geofence added successfully. " + latLng + " Radius: " + GEOFENCING_RADIUS + " meters"); + AppUtil.updatePatientGeofence(activity, latLng.latitude+"", latLng.longitude+"", + GEOFENCING_RADIUS+"", unit, message); + }) + .addOnFailureListener(e -> { + Log.d(GEOFENCE_TAG, "onFailure: Geofence couldn't be added: " + e.getLocalizedMessage() + " " + latLng + " Radius: " + GEOFENCING_RADIUS); + }); + + } + + @Override + public void onGeofenceDetailsFetched(@NonNull GeoFenceDetails geoFenceDetails) { + validateAndAddGeofence(geoFenceDetails); + } + @Override + public void onGeofenceDetailsFetchFailed(Throwable throwable, String message) { + Log.d(GEOFENCE_TAG, "onGeofenceDetailsFetchFailed: " + message); + + } } diff --git a/app/src/main/java/com/app/simplitend/patient_dashboard/DashBoardActivity.java b/app/src/main/java/com/app/simplitend/patient_dashboard/DashBoardActivity.java index 3af0169..db455df 100644 --- a/app/src/main/java/com/app/simplitend/patient_dashboard/DashBoardActivity.java +++ b/app/src/main/java/com/app/simplitend/patient_dashboard/DashBoardActivity.java @@ -1,7 +1,5 @@ package com.app.simplitend.patient_dashboard; -import static com.app.simplitend.patientgeofencing.GeoFenceHelper.GEOFENCE_TAG; - import android.Manifest; import android.app.role.RoleManager; import android.content.Context; @@ -11,26 +9,25 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.telecom.TelecomManager; -import android.util.Log; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import com.app.simplitend.R; import com.app.simplitend.apputils.AppUtil; -import com.app.simplitend.apputils.PatientDataCache; -import com.app.simplitend.caregiverdashboard.mvvm.CgHomeContracts; -import com.app.simplitend.caregiverdashboard.mvvm.models.GeoFenceDetails; +import com.app.simplitend.locationupdates.LocationService; -public class DashBoardActivity extends AppCompatActivity implements CgHomeContracts.GetGeoFenceCallback { +public class DashBoardActivity extends AppCompatActivity { protected PatientMainViewModel viewModel; protected ActivityResultLauncher finePermissionLauncher; + protected ActivityResultLauncher backgroundPermissionLauncher; @Override protected void onCreate(Bundle savedInstanceState) { @@ -39,17 +36,40 @@ public class DashBoardActivity extends AppCompatActivity implements CgHomeContra viewModel = new ViewModelProvider(this).get(PatientMainViewModel.class); - updateGeofenceDetails(); + backgroundPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), + isGranted -> { + if (isGranted) { + setGeofenceAndLocationUpdates(); + } + }); finePermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { // getting location updates - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - return; + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + viewModel.addLocationUpdates(this); + } + + // fine location permission is granted + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED){ + AppUtil.showSOSDecision(this, + "To enable caregivers to receive notifications even when the app is closed, go to the settings menu and select allow all the time", + "Settings", "No thanks", view -> { + // Settings click + backgroundPermissionLauncher.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION); + }, view -> { + // No thanks click + Toast.makeText(this, "Geofencing is off.", Toast.LENGTH_SHORT).show(); + }); + }else{ + setGeofenceAndLocationUpdates(); + } + } else { + setGeofenceAndLocationUpdates(); } - viewModel.addLocationUpdates(this); } else { // permission denied AppUtil.showAlert(this, @@ -62,6 +82,8 @@ public class DashBoardActivity extends AppCompatActivity implements CgHomeContra } }); + + registerForActivityResult(new ActivityResultContracts.RequestPermission(), result -> { @@ -86,6 +108,12 @@ public class DashBoardActivity extends AppCompatActivity implements CgHomeContra } + private void setGeofenceAndLocationUpdates() { + Intent intent = new Intent(this, LocationService.class); + intent.setAction(LocationService.ACTION_START_GEOFENCE); + ContextCompat.startForegroundService(this, intent); + } + @Override protected void attachBaseContext(Context newBase) { final Configuration configuration = new Configuration(newBase.getResources().getConfiguration()); @@ -93,41 +121,4 @@ public class DashBoardActivity extends AppCompatActivity implements CgHomeContra applyOverrideConfiguration(configuration); super.attachBaseContext(newBase); } - - // saves the geofence details - private void updateGeofenceDetails() { - // retrieving geofence - viewModel.getGeoFenceDetails(AppUtil.getPatientUid(this) + "", - "", "Bearer " + AppUtil.getPatientToken(this), - this); - } - - public void validateAndAddGeofence(GeoFenceDetails geoFenceDetails) { - if (geoFenceDetails.radius != null && geoFenceDetails.type != null) { - PatientDataCache.getPatientData(this, (patientData -> { - if (patientData != null) { - if (AppUtil.shouldAddPatientGeofence(this, - geoFenceDetails.radius, geoFenceDetails.type, patientData)) { - // should add a geofence - AppUtil.updatePatientGeofence(this, patientData.lat, patientData.lng, - geoFenceDetails.radius, geoFenceDetails.type, geoFenceDetails.message); - } else { - Log.d(GEOFENCE_TAG, "onGeofenceDetailsFetched: GEOFENCE DATA UP TO DATE"); - } - } - }), false); - } - - } - - @Override - public void onGeofenceDetailsFetched(@NonNull GeoFenceDetails geoFenceDetails) { - validateAndAddGeofence(geoFenceDetails); - } - - @Override - public void onGeofenceDetailsFetchFailed(Throwable throwable, String message) { - Log.d(GEOFENCE_TAG, "onGeofenceDetailsFetchFailed: " + message); - - } } \ No newline at end of file diff --git a/app/src/main/java/com/app/simplitend/patient_dashboard/PatientMainViewModel.java b/app/src/main/java/com/app/simplitend/patient_dashboard/PatientMainViewModel.java index 001547f..414eb35 100644 --- a/app/src/main/java/com/app/simplitend/patient_dashboard/PatientMainViewModel.java +++ b/app/src/main/java/com/app/simplitend/patient_dashboard/PatientMainViewModel.java @@ -1,13 +1,11 @@ package com.app.simplitend.patient_dashboard; -import static android.content.Context.ACTIVITY_SERVICE; import static com.app.simplitend.locationupdates.LocationService.LOCATION_INTERVAL_BASE_TIME; import static com.app.simplitend.locationupdates.LocationService.LOCATION_UPDATE_MIN_INTERVAL; import static com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver.LOCATION_REQUEST_TAG; import android.Manifest; import android.app.Activity; -import android.app.ActivityManager; import android.content.Intent; import android.os.Build; import android.os.Handler; diff --git a/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceBroadcastReceiver.java b/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceBroadcastReceiver.java index e9613b0..c6a1f12 100644 --- a/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceBroadcastReceiver.java +++ b/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceBroadcastReceiver.java @@ -79,17 +79,8 @@ public class GeoFenceBroadcastReceiver extends BroadcastReceiver { break; case Geofence.GEOFENCE_TRANSITION_ENTER: Log.d(GEOFENCE_TAG, "onReceive: ENTER"); - Notification notification = new NotificationCompat.Builder(context, LOCATION_NOTIFICATION_CHANNEL_ID) - .setSmallIcon(R.mipmap.ic_launcher_round) - .setContentTitle("Geofence entered") - .setPriority(NotificationCompat.PRIORITY_HIGH).build(); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - return; - } - notificationManager.notify(1023, notification); + AppUtil.updateSeniorOutOfGeofence(context, false); Intent locationServiceIntent = new Intent(context, LocationService.class); locationServiceIntent.setAction(LocationService.ACTION_START_LOCATION_UPDATES); @@ -103,17 +94,15 @@ public class GeoFenceBroadcastReceiver extends BroadcastReceiver { case Geofence.GEOFENCE_TRANSITION_EXIT: Log.d(GEOFENCE_TAG, "onReceive: EXIT"); - Notification notification1 = new NotificationCompat.Builder(context, LOCATION_NOTIFICATION_CHANNEL_ID) - .setSmallIcon(R.mipmap.ic_launcher_round) - .setContentTitle("Geofence exited") - .setPriority(NotificationCompat.PRIORITY_HIGH).build(); - - NotificationManagerCompat notificationManager1 = NotificationManagerCompat.from(context); - - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - return; + if (AppUtil.isSeniorOutOfGeofence(context)){ + // already out of geofence + // meaning the location updates were received early than the geofence trigger + // and the caregiver has already been sent notifications + // thus, not sending them again + break; } - notificationManager1.notify(1032, notification1); + + AppUtil.updateSeniorOutOfGeofence(context, true); notifyOutOfGeofence(context, String.format(Locale.getDefault(), "%.2f", distance)); @@ -221,7 +210,7 @@ public class GeoFenceBroadcastReceiver extends BroadcastReceiver { // getting faster location updates as patient is out of geofence Intent intent = new Intent(context, LocationService.class); intent.setAction(LocationService.ACTION_START_LOCATION_UPDATES); - intent.putExtra(LOCATION_UPDATE_MIN_INTERVAL, 5 * 1000); // every 5 seconds + intent.putExtra(LOCATION_UPDATE_MIN_INTERVAL, 10 * 1000); // every 10 seconds if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent); } else { diff --git a/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceHelper.java b/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceHelper.java index 995e3d9..0a83b23 100644 --- a/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceHelper.java +++ b/app/src/main/java/com/app/simplitend/patientgeofencing/GeoFenceHelper.java @@ -40,7 +40,6 @@ public class GeoFenceHelper extends ContextWrapper { .setRequestId(GEOFENCE_ID) .setTransitionTypes(transitionType) .setExpirationDuration(Geofence.NEVER_EXPIRE) - .setLoiteringDelay(5000) // This does nothing if the transition is not DWELL .build(); }