Reputation: 37
I am a novice in Android development. I tried to develop an Android app which will show a Google Map with a fixed Geofence Area and the current location of the concerned person. Whenever he / she leaves or enters that particular geofence region a notification will be shown. After searching various forums and stackoverflow for ideas I somehow manged to develop the application. But I am now facing the problem that it shows the notfication about the Entry / exit of Geofence area only when the app is open. If it is minimized and swiped out it doesn't run in background. I used GeofenceTransitionsJobIntentService for geofence transition changes. I think that I had done some silly mistake so it's not working in background. So please help me out from this problem.
Here's the full code. Any ideas where I'm going wrong ? Thanks in Advance
My codes:
GeofenceTransitionsJobIntentService.Java
public class GeofenceTransitionsJobIntentService extends JobIntentService {
private static final int JOB_ID = 573;
private static final String TAG = "GeofenceTransitionsIS";
private static final String CHANNEL_ID = "channel_01";
/**
* Convenience method for enqueuing work in to this service.
*/
public static void enqueueWork(Context context, Intent intent) {
enqueueWork(context, GeofenceTransitionsJobIntentService.class, JOB_ID, intent);
}
/**
* Handles incoming intents.
* @param intent sent by Location Services. This Intent is provided to Location
* Services (inside a PendingIntent) when addGeofences() is called.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onHandleWork(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Get the geofences that were triggered. A single event can trigger multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(geofenceTransition,
triggeringGeofences);
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails);
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition));
}
}
/**
* Gets transition details and returns them as a formatted string.
*
* @param geofenceTransition The ID of the geofence transition.
* @param triggeringGeofences The geofence(s) triggered.
* @return The transition details formatted as String.
*/
private String getGeofenceTransitionDetails(
int geofenceTransition,
List<Geofence> triggeringGeofences) {
String geofenceTransitionString = getTransitionString(geofenceTransition);
// Get the Ids of each geofence that was triggered.
ArrayList<String> triggeringGeofencesIdsList = new ArrayList<>();
for (Geofence geofence : triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.getRequestId());
}
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the MainActivity.
*/
private void sendNotification(String notificationDetails) {
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Android O requires a Notification Channel.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.app_name);
// Create the channel for the notification
NotificationChannel mChannel =
new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
// Set the Notification Channel for the Notification Manager.
mNotificationManager.createNotificationChannel(mChannel);
}
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(getApplicationContext(), MapsActivity.class);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Add the main Activity to the task stack as the parent.
stackBuilder.addParentStack(MapsActivity.class);
// Push the content Intent onto the stack.
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack.
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
// Define the notification settings.
builder.setSmallIcon(R.drawable.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setColor(Color.RED)
.setOngoing(false)
.setPriority(Notification.PRIORITY_DEFAULT)
.setContentTitle(notificationDetails)
.setTicker(notificationDetails)
.setContentText(getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Set the Channel ID for Android O.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID); // Channel ID
}
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
/**
* Maps geofence transition types to their human-readable equivalents.
*
* @param transitionType A transition type constant defined in Geofence
* @return A String indicating the type of transition
*/
private String getTransitionString(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return getString(R.string.geofence_transition_entered);
case Geofence.GEOFENCE_TRANSITION_EXIT:
return getString(R.string.geofence_transition_exited);
default:
return getString(R.string.unknown_geofence_transition);
}
}
}
GeofenceBroadcastReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Enqueues a JobIntentService passing the context and intent as parameters
GeofenceTransitionsJobIntentService.enqueueWork(context, intent);
}
}
MapsActivity.java
public class MapsActivity extends AppCompatActivity implements
GoogleMap.OnMyLocationButtonClickListener, OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener, OnCompleteListener<Void>
{
private static final String TAG = MapsActivity.class.getSimpleName();
static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;
static final int RADIUS = 500;
private LocationManager locationManager;
private String provider;
private Location location;
private GoogleMap mMap;
private Circle circle;
private PendingIntent geofencePendingIntent;
private GeofencingClient geofencingClient;
private GoogleApiClient googleApiClient;
private boolean isContinue = false;
private boolean isGPS = false;
private LocationRequest locationRequest;
private final int UPDATE_INTERVAL = 2 * 60 * 1000;
private final int FASTEST_INTERVAL = 20 * 1000;
private final int NOTIFICATION_RESPONSIVENESS_TIME = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
geofencingClient = LocationServices.getGeofencingClient(this);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
createGoogleApi();
new GpsUtils(this).turnGPSOn(new GpsUtils.onGpsListener() {
@Override
public void gpsStatus(boolean isGPSEnable) {
// turn on GPS
isGPS = isGPSEnable;
}
});
if (!checkPermissions()) {
requestPermissions();
}
}
@Override
public void onMapReady(GoogleMap googleMap)
{
Log.d(TAG,"onMapReady()");
mMap = googleMap;
mMap.setOnMyLocationButtonClickListener(this);
addGeofence(getMyLocation(), RADIUS);
drawCircle(getMyLocation(), RADIUS);
markerForGeofence(getMyLocation());
}
private void createGoogleApi()
{
if(googleApiClient==null)
{
googleApiClient=new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
}
@Override
public void onStart() {
super.onStart();
googleApiClient.connect();
if (checkPermissions()) {
// removeGeofence();
addGeofence(getMyLocation(), RADIUS);
// drawCircle(getMyLocation(), RADIUS);
// markerForGeofence(getMyLocation());
} else {
requestPermissions();
}
}
@Override
public void onStop() {
super.onStop();
googleApiClient.disconnect();
}
@Override
public void onConnected(@Nullable Bundle bundle)
{
Log.i(TAG, "onConnected()");
getLastKnownLocation();
addGeofence(getMyLocation(), RADIUS);
drawCircle(getMyLocation(), RADIUS);
markerForGeofence(getMyLocation());
}
@Override
public void onConnectionSuspended(int i)
{
Log.w(TAG, "onConnectionSuspended()");
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult)
{
Log.w(TAG, "onConnectionFailed()");
}
// Get last known location
private void getLastKnownLocation() {
Log.d(TAG, "getLastKnownLocation()");
if ( checkPermissions() ) {
location = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
if ( location != null ) {
Log.i(TAG, "LasKnown location. " +
"Long: " + location.getLongitude() +
" | Lat: " + location.getLatitude());
writeLocation();
startLocationUpdates();
} else {
Log.w(TAG, "No location retrieved yet");
startLocationUpdates();
}
}
else requestPermissions();
}
// Start location Updates
private void startLocationUpdates(){
Log.i(TAG, "startLocationUpdates()");
locationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(UPDATE_INTERVAL)
.setFastestInterval(FASTEST_INTERVAL);
if ( checkPermissions() )
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this);
}
@Override
public void onLocationChanged(Location location) {
Log.d(TAG, "onLocationChanged ["+location+"]");
location = location;
writeActualLocation(location);
addGeofence(getMyLocation(), RADIUS);
}
// Write location coordinates on UI
private void writeActualLocation(Location location) {
markerLocation(new LatLng(location.getLatitude(), location.getLongitude()));
}
private void writeLocation() {
writeActualLocation(location);
}
private Marker locationMarker;
// Create a Location Marker
private void markerLocation(LatLng latLng) {
Log.i(TAG, "markerLocation("+latLng+")");
String title = "Your Current Location("+latLng.latitude + ", " + latLng.longitude+")";
MarkerOptions markerOptions = new MarkerOptions()
.position(latLng)
.title(title);
if ( mMap!=null ) {
// Remove the anterior marker
if ( locationMarker != null )
locationMarker.remove();
locationMarker = mMap.addMarker(markerOptions);
float zoom = 14f;
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, zoom);
mMap.animateCamera(cameraUpdate);
}
}
private Marker geoFenceMarker;
// Create a marker for the geofence creation
private void markerForGeofence(LatLng latLng) {
Log.i(TAG, "markerForGeofence("+latLng+")");
String title = "Your Geofence Area("+latLng.latitude + ", " + latLng.longitude+")";
// Define marker options
MarkerOptions markerOptions = new MarkerOptions()
.position(latLng)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
.title(title);
if ( mMap!=null ) {
// Remove last geoFenceMarker
if (geoFenceMarker != null)
geoFenceMarker.remove();
geoFenceMarker = mMap.addMarker(markerOptions);
}
}
/**
* Return the current state of the permissions needed.
*/
private boolean checkPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED )
{
return false;
}
else
{
return true;
}
}
private void requestPermissions() {
boolean shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION);
boolean shouldProvideRationale1 =
ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION);
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
if (shouldProvideRationale || shouldProvideRationale1) {
Log.i(TAG, "Displaying permission rationale to provide additional context.");
Snackbar.make(
findViewById(R.id.activity_main),
R.string.permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
// Request permission
ActivityCompat.requestPermissions(MapsActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSIONS_REQUEST_LOCATION);
}
})
.show();
} else {
Log.i(TAG, "Requesting permission");
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(MapsActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION},
MY_PERMISSIONS_REQUEST_LOCATION);
}
}
// For creating GeoFence.
private Geofence createGeofence(LatLng latLng, int radiusMeters) {
return new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId("1")
.setCircularRegion(latLng.latitude, latLng.longitude, radiusMeters)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setNotificationResponsiveness(NOTIFICATION_RESPONSIVENESS_TIME)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
}
private GeofencingRequest getGeofencingRequest(LatLng latLng, int radiusMeters) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_EXIT);
builder.addGeofence(createGeofence(latLng, radiusMeters));
return builder.build();
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
Log.i(TAG, "onRequestPermissionResult");
if (requestCode == MY_PERMISSIONS_REQUEST_LOCATION) {
if (grantResults.length <= 0) {
// If user interaction was interrupted, the permission request is cancelled and you
// receive empty arrays.
Log.i(TAG, "User interaction was cancelled.");
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED && grantResults[2] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) {
getLastKnownLocation();
}
} else {
// Permission denied.
// setButtonsState(false);
Snackbar.make(
findViewById(R.id.activity_main),
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.settings, new View.OnClickListener() {
@Override
public void onClick(View view) {
// Build intent that displays the App settings screen.
Intent intent = new Intent();
intent.setAction(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}).show();
}
}
}
@Override
public boolean onMyLocationButtonClick() {
// Return false so that we don't consume the event and the default behavior still occurs
// (the camera animates to the user's current position).
/* if (circle != null)
circle.remove();
drawCircle(getMyLocation(), RADIUS);*/
return false;
}
private void drawCircle(LatLng latLng, int radius) {
circle = mMap.addCircle(new CircleOptions()
.center(latLng)
.radius(radius)
.strokeWidth(0f)
.fillColor(0x55FF0000));
}
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (geofencePendingIntent != null) {
return geofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return geofencePendingIntent;
}
private void removeGeofence() {
geofencingClient.removeGeofences(getGeofencePendingIntent()).addOnCompleteListener(this);
}
private void addGeofence(LatLng latLng, int radiusMeters) {
geofencingClient.addGeofences(getGeofencingRequest(latLng, radiusMeters), getGeofencePendingIntent())
.addOnCompleteListener(this);
}
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
} else {
}
}
}
Upvotes: 2
Views: 1986
Reputation: 1517
On Android 8.0 (API level 26) and higher, if an app is running in the background while monitoring a geofence, then the device responds to geofencing events every couple of minutes. To learn how to adapt your app to these response limits, see Background Location Limits.
You might need to run a sticky foreground service for your app to be running continuously.
Upvotes: 0
Reputation: 864
Run Foreground service, when App goes to background. This solves your problem.
Upvotes: 2