I implemented Geofence in android application. I followed this link to implement 'Geofence' in app. I am using 'Retrofit' library to call 'HTTP' request.
App has following permissions :
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Here is my 'IntentService' code :
public class GeofenceService extends IntentService
{
private static final String TAG = GeofenceService.class.getName();
public static final int GEOFENCE_NOTIFICATION_ID = 0;
public GeofenceService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
// Retrieve the Geofencing intent
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
createLoggerFile();
// Handling errors
if ( geofencingEvent.hasError() ) {
String errorMsg = getErrorString(geofencingEvent.getErrorCode() );
Logger.Important(true, TAG, "onHandleIntent() :: errorMessage : "+errorMsg );
return;
}
// Retrieve GeofenceTrasition
int geoFenceTransition = geofencingEvent.getGeofenceTransition();
// Check if the transition type
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL)
{
Log.d(TAG, "onHandleIntent() :: geoFenceTransition : " + geoFenceTransition);
// Get the geofence that were triggered
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Create a detail message with Geofences received
String geofenceTransitionDetails = getGeofenceTrasitionDetails(geoFenceTransition, triggeringGeofences );
// Send notification details as a String
sendNotification( geofenceTransitionDetails );
}
}
// Create a detail message with Geofences received
private String getGeofenceTrasitionDetails(int geoFenceTransition, List<Geofence> triggeringGeofences) {
// get the ID of each geofence triggered
ArrayList<String> triggeringGeofencesList = new ArrayList<>();
for ( Geofence geofence : triggeringGeofences ) {
triggeringGeofencesList.add( geofence.getRequestId() );
pingGoogle(); // here is I am pinging google
callingHttpRequest(); // calling Http request. Also I called this request through application class, but still it is not worked in background.
}
String status = null;
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER )
status = "Entering ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT )
status = "Exiting ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL )
status = "Staying ";
return status + TextUtils.join( ", ", triggeringGeofencesList);
}
// Send a notification
private void sendNotification( String msg ) {
Log.d( TAG, "sendNotification: " + msg );
// Intent to start the main Activity
Intent notificationIntent = new Intent(getApplicationContext(), DrawerActivity.class);;
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(DrawerActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Creating and sending Notification
NotificationManager notificatioMng =
(NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
notificatioMng.notify(
GEOFENCE_NOTIFICATION_ID,
createNotification(msg, notificationPendingIntent));
}
// Create a notification
private Notification createNotification(String msg, PendingIntent notificationPendingIntent) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
notificationBuilder
.setSmallIcon(R.drawable.ic_phi_notification_logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.geo))
.setColor(Converter.getColor(getApplicationContext(), R.color.default_pure_cyan))
.setContentTitle(JsonKey.TRIGGER)
.setContentText(msg)
.setContentIntent(notificationPendingIntent)
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
.setAutoCancel(true);
return notificationBuilder.build();
}
// Handle errors
private static String getErrorString(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "GeoFence not available";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "Too many GeoFences";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "Too many pending intents";
default:
return "Unknown error.";
}
}
private void callingHttpRequest() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10 / 2, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory().getSocketFactory())
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
API api = retrofit.create(***.class);
Call<ResponseBody> req = api.callGeofencingTrigger(***);
req.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
try {
String string = response.body().string();
Log.d (TAG, "onResponse() :: success");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
Log.d (TAG, "onFailure() :: t : "t.getMessage());
}
});
}
}
Whenever device got geofence trigger, It works fine and gives proper trigger notifications while app is in background or in foreground(enter/dwell/leave) or even if user kills the app from recent tasks. When I call HTTP request, when application is in foreground then it works fine and It prints success on log.
onResponse() :: success
But when application is killed from recent tasks and device got any geofence trigger(enter/dwell/leave) then HTTP request is not executed properly. It gives :
onFailure() :: t :
</br>java.net.UnknownHostException: Unable to resolve host
"host_name": No address associated with hostname
where host_name is server address.
I ping google or 8.8.8.8 ip from the background service. Still facing same issues. This things is also works fine when app is in foreground but after killing app it does not works.
So, why this error? Does network communication is not calling when app is not in recent tasks?
<-------------------------------------------------------------------------------------------------------------------------->
I tried following things. After getting answers from @Xavier and @Stevensen
I am using firebase-jobscheduler in my application for calling HTTP
request. Here is my code :
In my manifest I added following service :
<service
android:exported="false"
android:name="com.****.service.TriggerJobService">
<intent-filter>
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
</intent-filter>
</service>
This is my modified GeofenceService class. I just removed callingHttpRequest()
and added schedule job by calling scheduleJob()
function in getGeofenceTrasitionDetails()
function. And code is same as it is.
public class GeofenceService extends IntentService
{
private static final String TAG = GeofenceService.class.getName();
public static final int GEOFENCE_NOTIFICATION_ID = 0;
public GeofenceService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
// Retrieve the Geofencing intent
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
createLoggerFile();
// Handling errors
if ( geofencingEvent.hasError() ) {
String errorMsg = getErrorString(geofencingEvent.getErrorCode() );
Logger.Important(true, TAG, "onHandleIntent() :: errorMessage : "+errorMsg );
return;
}
// Retrieve GeofenceTrasition
int geoFenceTransition = geofencingEvent.getGeofenceTransition();
// Check if the transition type
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL)
{
Log.d(TAG, "onHandleIntent() :: geoFenceTransition : " + geoFenceTransition);
// Get the geofence that were triggered
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Create a detail message with Geofences received
String geofenceTransitionDetails = getGeofenceTrasitionDetails(geoFenceTransition, triggeringGeofences );
// Send notification details as a String
sendNotification( geofenceTransitionDetails );
}
}
// Create a detail message with Geofences received
private String getGeofenceTrasitionDetails(int geoFenceTransition, List<Geofence> triggeringGeofences) {
// get the ID of each geofence triggered
ArrayList<String> triggeringGeofencesList = new ArrayList<>();
for ( Geofence geofence : triggeringGeofences ) {
triggeringGeofencesList.add( geofence.getRequestId() );
scheduleJob(); // <code>**Here I schedule job**</code>
}
String status = null;
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER )
status = "Entering ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT )
status = "Exiting ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL )
status = "Staying ";
return status + TextUtils.join( ", ", triggeringGeofencesList);
}
// Send a notification
private void sendNotification( String msg ) {
Log.d( TAG, "sendNotification: " + msg );
// Intent to start the main Activity
Intent notificationIntent = new Intent(getApplicationContext(), DrawerActivity.class);;
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(DrawerActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Creating and sending Notification
NotificationManager notificatioMng =
(NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
notificatioMng.notify(
GEOFENCE_NOTIFICATION_ID,
createNotification(msg, notificationPendingIntent));
}
// Create a notification
private Notification createNotification(String msg, PendingIntent notificationPendingIntent) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
notificationBuilder
.setSmallIcon(R.drawable.ic_phi_notification_logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.geo))
.setColor(Converter.getColor(getApplicationContext(), R.color.default_pure_cyan))
.setContentTitle(JsonKey.TRIGGER)
.setContentText(msg)
.setContentIntent(notificationPendingIntent)
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
.setAutoCancel(true);
return notificationBuilder.build();
}
// Handle errors
private static String getErrorString(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "GeoFence not available";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "Too many GeoFences";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "Too many pending intents";
default:
return "Unknown error.";
}
}
private void scheduleJob()
{
Bundle bundle = new Bundle();
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
Job.Builder builder = dispatcher.newJobBuilder();
builder.setExtras(bundle);
builder.setTag(requestId);
builder.setService(TriggerJobService.class);
builder.setTrigger(Trigger.executionWindow(10, 30));
builder.setReplaceCurrent(true);
builder.addConstraint(Constraint.DEVICE_CHARGING);
builder.addConstraint(Constraint.ON_ANY_NETWORK);
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
dispatcher.mustSchedule(builder.build());
}
}
This is code my TriggerJobService :
public class TriggerJobService extends JobService
{
private static final String TAG = TriggerJobService.class.getName();
private int count;
@Override
public boolean onStartJob(JobParameters job)
{
Log.d(TAG, "onStartJob() :: " + job.getTag());
// Return true as there's more work to be done with this job.
//TODO have to send request to cloud
Bundle bundle = job.getExtras();
callingHttpRequest(); // here is I am calling 'HTTP' request
return true;
}
@Override
public boolean onStopJob(JobParameters job)
{
Log.d(TAG, "onStopJob() :: " + job.getTag());
// Return false to drop the job.
return false;
}
private void callingHttpRequest() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10 / 2, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory().getSocketFactory())
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
API api = retrofit.create(***.class);
Call<ResponseBody> req = api.callGeofencingTrigger(***);
req.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
try {
String string = response.body().string();
Log.d (TAG, "onResponse() :: success");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
Log.d (TAG, "onFailure() :: t : "t.getMessage());
}
});
}
}
Again it is calling the same. It works fine and gives proper trigger notifications while app is in background or in foreground(enter/dwell/leave) or even if user kills the app from recent tasks. Also it is scheduling proper job. And calling HTTP
request, when application is in foreground then it works fine and It prints success on log.
onResponse() :: success
But when application is killed from recent tasks and device got any geofence trigger(enter/dwell/leave) then application schedules job and calling HTTP
request is not executed properly. It gives :
onFailure() :: t :
</br>java.net.UnknownHostException: Unable to resolve host
"host_name": No address associated with hostname
So as per @Xavier & @Stevensen answers my app is not wake up network if it kills from recent tasks. I tried with firbase-JobSchedule
but still facing same above error. Does application need any special permission
to call HTTP
request while app kills from recent tasks? or is FCM
is better options for that. But still have same question whether the FCM
will works even if app kills from recent tasks? does FCM
will wake up network to send message to server from client?