I am trying to create a geofence with background service for monitor. The geofence create successfully and work when apps Activity is open but when close the app geofence not wo
From Android Oreo you can't create a long running service as background service. So you have to create a foreground service which the service must bind to the current Activity
Another problem is the location update. When you using geoQuery
inside a service you have to update location also in the service. When a location update trigger you must pass this update to activity then only the UI can update. In my solution I am using an interface to update the UI. You can also use broadcast listeners.
When you create service and bind it to activity, then you can use Geofence inside service
public void startService(LatLng latLng, double radius) {
if (!isServiceRunning) {
isServiceRunning = true;
} else {
Log.e(TAG, "startTimer request for an already running timer");
}
if (geoQuery!=null){
geoQuery.removeAllListeners();
}
geoQuery = geoFire.queryAtLocation(new GeoLocation(latLng.latitude, latLng.longitude), 2f);
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
sendNotification("MRF", String.format("%s entered the dangerous area", key));
}
@Override
public void onKeyExited(String key) {
sendNotification("MRF", String.format("%s exit the dangerous area", key));
}
@Override
public void onKeyMoved(String key, GeoLocation location) {
Log.d("MOVE", String.format("%s move within the dangerous area [%f/%f]", key, location.latitude, location.longitude));
}
@Override
public void onGeoQueryReady() {
}
@Override
public void onGeoQueryError(DatabaseError error) {
Log.d("ERROR", "" + error);
}
});
}
You can call this method from your activity as
geoService.startService(dangerous_area,2000);
This will work even if your app is destroyed.
The complete code is as follows
public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
//Play Service Location
private static final int MY_PERMISSION_REQUEST_CODE = 7192;
private static final int PLAY_SERVICE_RESULATION_REQUEST = 300193;
private Location mLastLocaiton;
private static int UPDATE_INTERVAL = 5000;
private static int FATEST_INTERVAL = 3000;
private static int DISPLACEMENT = 10;
Marker mCurrent;
VerticalSeekBar mVerticalSeekBar;
private static final String TAG = MapsActivity.class.getSimpleName();
private GeoService geoService;
private boolean serviceBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
mVerticalSeekBar = (VerticalSeekBar) findViewById(R.id.verticalSeekBar);
mVerticalSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mMap.animateCamera(CameraUpdateFactory.zoomTo(progress), 1500, null);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case MY_PERMISSION_REQUEST_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (checkPlayService()) {
geoService.buildGoogleApiClient();
geoService.createLocationRequest();
geoService.displayLocation();
geoService.setLocationChangeListener(new GeoService.LocationChangeListener() {
@Override
public void onLocationChange(Location location) {
if (mCurrent != null)
mCurrent.remove();
mCurrent = mMap.addMarker(new MarkerOptions()
.position(new LatLng(location.getLatitude(), location.getLongitude()))
.title("You"));
LatLng coordinate = new LatLng(location.getLatitude(), location.getLongitude());
CameraUpdate yourLocation = CameraUpdateFactory.newLatLngZoom(coordinate, 12);
mMap.animateCamera(yourLocation);
}
});
}
}
break;
}
}
private void setUpdateLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
}, MY_PERMISSION_REQUEST_CODE);
} else {
if (checkPlayService()) {
geoService.buildGoogleApiClient();
geoService.createLocationRequest();
geoService.displayLocation();
geoService.setLocationChangeListener(new GeoService.LocationChangeListener() {
@Override
public void onLocationChange(Location location) {
if (mCurrent != null)
mCurrent.remove();
mCurrent = mMap.addMarker(new MarkerOptions()
.position(new LatLng(location.getLatitude(), location.getLongitude()))
.title("You"));
LatLng coordinate = new LatLng(location.getLatitude(), location.getLongitude());
CameraUpdate yourLocation = CameraUpdateFactory.newLatLngZoom(coordinate, 12);
mMap.animateCamera(yourLocation);
}
});
}
}
}
private boolean checkPlayService() {
GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance();
int result = googleAPI.isGooglePlayServicesAvailable(this);
if (result != ConnectionResult.SUCCESS) {
if (googleAPI.isUserResolvableError(result)) {
googleAPI.getErrorDialog(this, result, PLAY_SERVICE_RESULATION_REQUEST).show();
} else {
Toast.makeText(this, "This Device is not supported.", Toast.LENGTH_SHORT).show();
}
return false;
}
return true;
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
LatLng dangerous_area = new LatLng(8.5324236, 76.8842189);
mMap.addCircle(new CircleOptions()
.center(dangerous_area)
.radius(2000)
.strokeColor(Color.BLUE)
.fillColor(0x220000FF)
.strokeWidth(5.0f));
geoService.startService(dangerous_area,2000);
}
@Override
protected void onStart() {
super.onStart();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Starting and binding service");
}
Intent i = new Intent(this, GeoService.class);
startService(i);
bindService(i, mConnection, 0);
}
@Override
protected void onStop() {
super.onStop();
if (serviceBound) {
// If a timer is active, foreground the service, otherwise kill the service
if (geoService.isServiceRunning()) {
geoService.foreground();
} else {
stopService(new Intent(this, GeoService.class));
}
// Unbind the service
unbindService(mConnection);
serviceBound = false;
}
}
/**
* Callback for service binding, passed to bindService()
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Service bound");
}
GeoService.RunServiceBinder binder = (GeoService.RunServiceBinder) service;
geoService = binder.getService();
serviceBound = true;
// Ensure the service is not in the foreground when bound
geoService.background();
setUpdateLocation();
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(MapsActivity.this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Service disconnect");
}
serviceBound = false;
}
};
public static class GeoService extends Service implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private LocationRequest mLocationRequest;
private GoogleApiClient mGoogleApiClient;
private Location mLastLocation;
private DatabaseReference ref;
private GeoFire geoFire;
private LocationChangeListener mLocationChangeListener;
private static final String TAG = GeoService.class.getSimpleName();
// Is the service tracking time?
private boolean isServiceRunning;
// Foreground notification id
private static final int NOTIFICATION_ID = 1;
// Service binder
private final IBinder serviceBinder = new RunServiceBinder();
private GeoQuery geoQuery;
public class RunServiceBinder extends Binder {
GeoService getService() {
return GeoService.this;
}
}
@Override
public void onCreate() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Creating service");
}
ref = FirebaseDatabase.getInstance().getReference("MyLocation");
geoFire = new GeoFire(ref);
isServiceRunning = false;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Starting service");
}
return Service.START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Binding service");
}
return serviceBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Destroying service");
}
}
/**
* Starts the timer
*/
public void startService(LatLng latLng, double radius) {
if (!isServiceRunning) {
isServiceRunning = true;
} else {
Log.e(TAG, "startService request for an already running Service");
}
if (geoQuery!=null){
geoQuery.removeAllListeners();
}
geoQuery = geoFire.queryAtLocation(new GeoLocation(latLng.latitude, latLng.longitude), 2f);
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
sendNotification("MRF", String.format("%s entered the dangerous area", key));
}
@Override
public void onKeyExited(String key) {
sendNotification("MRF", String.format("%s exit the dangerous area", key));
}
@Override
public void onKeyMoved(String key, GeoLocation location) {
Log.d("MOVE", String.format("%s move within the dangerous area [%f/%f]", key, location.latitude, location.longitude));
}
@Override
public void onGeoQueryReady() {
}
@Override
public void onGeoQueryError(DatabaseError error) {
Log.d("ERROR", "" + error);
}
});
}
/**
* Stops the timer
*/
public void stopService() {
if (isServiceRunning) {
isServiceRunning = false;
geoQuery.removeAllListeners();
} else {
Log.e(TAG, "stopTimer request for a timer that isn't running");
}
}
/**
* @return whether the service is running
*/
public boolean isServiceRunning() {
return isServiceRunning;
}
/**
* Place the service into the foreground
*/
public void foreground() {
startForeground(NOTIFICATION_ID, createNotification());
}
/**
* Return the service to the background
*/
public void background() {
stopForeground(true);
}
/**
* Creates a notification for placing the service into the foreground
*
* @return a notification for interacting with the service when in the foreground
*/
private Notification createNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle("Service is Active")
.setContentText("Tap to return to the Map")
.setSmallIcon(R.mipmap.ic_launcher);
Intent resultIntent = new Intent(this, MapsActivity.class);
PendingIntent resultPendingIntent =
PendingIntent.getActivity(this, 0, resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(resultPendingIntent);
return builder.build();
}
private void sendNotification(String title, String content) {
Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(content);
NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MapsActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
builder.setContentIntent(contentIntent);
Notification notification = builder.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.defaults |= Notification.DEFAULT_SOUND;
manager.notify(new Random().nextInt(), notification);
}
@Override
public void onConnected(@Nullable Bundle bundle) {
displayLocation();
startLocationUpdate();
}
private void startLocationUpdate() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}
@Override
public void onConnectionSuspended(int i) {
mGoogleApiClient.connect();
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
@Override
public void onLocationChanged(Location location) {
mLastLocation = location;
displayLocation();
}
interface LocationChangeListener {
void onLocationChange(Location location);
}
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(FATEST_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setSmallestDisplacement(DISPLACEMENT);
}
private void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
mGoogleApiClient.connect();
}
private void displayLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (mLastLocation != null) {
final double latitude = mLastLocation.getLatitude();
final double longitude = mLastLocation.getLongitude();
geoFire.setLocation("You", new GeoLocation(latitude, longitude), new GeoFire.CompletionListener() {
@Override
public void onComplete(String key, DatabaseError error) {
if (mLocationChangeListener!=null) {
mLocationChangeListener.onLocationChange(mLastLocation);
}
}
});
Log.d("MRF", String.format("Your last location was chaged: %f / %f", latitude, longitude));
} else {
Log.d("MRF", "Can not get your location.");
}
}
public void setLocationChangeListener(LocationChangeListener mLocationChangeListener) {
this.mLocationChangeListener = mLocationChangeListener;
}
}
}
Don't forget to add service into manifest
<service android:name=".MapsActivity$GeoService" />
Full source code github.com/vinayakb73/GeoFence-GeoFire