15

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?

Mangesh Sambare
  • 564
  • 2
  • 20
  • Please provide info of device and Os version. – User10001 Aug 13 '17 at 21:58
  • I tried it on one-plus-two (OS-version 6.0), Xiomi-Mi4i(OS-version 5.0.2) devices. – Mangesh Sambare Aug 14 '17 at 05:56
  • Are you having the same issue in the 5.0.2 Xiaomi device? If so, the issue is not because of Doze mode (at least on this device), as this was added in 6.0 "Doze and App Standby manage the behavior of all apps running on Android 6.0 or higher" https://developer.android.com/training/monitoring-device-state/doze-standby.html Anyway, my previous advice of batching the event upload if possible is still valid. – Xavier Rubio Jansana Aug 14 '17 at 12:23
  • Finally my problem solved. Please have look to [this](https://stackoverflow.com/questions/45506162/geofencing-http-request-failed-while-sending-through-the-background-service-g/45749645#45749645). It will help to identify problems. – Mangesh Sambare Aug 18 '17 at 06:16

5 Answers5

5

Maybe your app gets blocked to use network by Androids doze mode and/or app standby. Check Optimizing for Doze and App Standby.

A possible solution is to setup an alarm with the AlarmManager. Android will schedule the alarms processing in a maintenance window where you are allowed to use network.

Stevensen
  • 151
  • 3
4

The explanation by @Stevensen about Doze mode being the reason of the failure is more likely the cause. In the documentation you can read:

The following restrictions apply to your apps while in Doze: Network access is suspended...

I'll suggest to store the events in a DB, and schedule a job to upload them to the server by using JobScheduler (API 21+, see a tutorial here) or, if you need to support older devices, by using this replacement firebase-jobdispatcher (which provides a JobScheduler-compatible API by wrapping GCM Network Manager).

I'll suggest to set a condition of network is needed: .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) and probably a .setPeriodic(long intervalMillis) to limit the number of times it happens (for example, upload at most once per hour).

As long as no realtime is required, it's a better approach for the user experience to save battery: Doze mode will help the device to save battery life, and JobScheduler will allow to batch uploads and just wake up the radio from time to time, saving battery life. See this quick video for the rationale.

Xavier Rubio Jansana
  • 5,998
  • 1
  • 23
  • 46
  • Thanks for replying. I tried firebase JobScheduler . It works fine in app in foreground & app in background. But still facing same issue while sending request when app kills from recent task & got some trigger. It gives
    onFailure() :: t : java.net.UnknownHostException: Unable to resolve host "host_name": No address associated with hostname
    – Mangesh Sambare Aug 16 '17 at 08:01
  • When you write `"host_name"`, is it literal? Or is `"whatever.myhost.com"` and you have replaced it with a placeholder to avoid providing private information? – Xavier Rubio Jansana Aug 16 '17 at 08:10
  • Yup. I don't want to share my private domain name. It is like `"whatever.myhost.com"` – Mangesh Sambare Aug 16 '17 at 08:11
  • Ok, just wanted to make sure it was not a silly mistake :) Could you please update your code or at least add the relevant changes (the call to JobScheduler and the handler)? – Xavier Rubio Jansana Aug 16 '17 at 08:31
  • One additional thought: `.readTimeout(10, TimeUnit.SECONDS)` and `.connectTimeout(10 / 2, TimeUnit.SECONDS)` maybe are too small. 5 seconds (10 / 2 in `connect`) could be too small in some cases (maybe radio is off + network latency + ...). – Xavier Rubio Jansana Aug 16 '17 at 08:33
  • I edited my question. Please look into it. Ok for time-out I will be change from 5 to 15 seconds and let you know. – Mangesh Sambare Aug 16 '17 at 09:01
  • I've looked at your code and everything seems fine. The only thing which may make it more resilient will be to add a retry strategy `.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)`. But, that being said, the only thing that now comes into my mind is the timeout, as you're already requesting the job to be executed when network is available. Which OS version are you using for this tests? Still 6.0+? – Xavier Rubio Jansana Aug 16 '17 at 13:50
  • 1
    I've also noticed that you're adding two constraints, which are mutually exclusive `builder.addConstraint(Constraint.ON_ANY_NETWORK);`and `builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);`. You should probably keep only the last one. – Xavier Rubio Jansana Aug 16 '17 at 13:56
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/152111/discussion-between-sam-and-xavier-rubio-jansana). – Mangesh Sambare Aug 17 '17 at 05:38
  • 1
    Thanks for your answer. I solved my problem. Please see [this](https://stackoverflow.com/questions/45506162/geofencing-http-request-failed-while-sending-through-the-background-service-g/45749645#45749645) answer – Mangesh Sambare Aug 18 '17 at 06:14
3

Finally my problem solved. Thanks to @Stevensen, @Xavier and one of my Friend who helps me to identify the problem. It is related to doze mode.


Some mobiles manufactures(Xiomi, Huawei etc) implemented SmartManager to optimize battery consumption. They have a kind of battery manager that kill apps, and when an app is killed, scheduled alarms are canceled and also they did not detect any active network or blocks network call from background service. Because manufactures blames non trusted apps for power consumption. Facebook, Whats App, etc apps are trusted and they are whitelisted by manufactures. That's why they are able to call network event even if app killed.


Still I did not find any solution for that.So temporary I overcome this problem for Xiomi devices. I keep out my app from battery saving restriction than its working correctly by doing following things:

settings--> battery -> Power --> App battery saver --> your app 
Now select No restrictions( for Background settings) then Allow option for Background location 


For android M version and above that, app have to ask permission :

Intent intent = new Intent();
String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName))
    intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
else {
    intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
    intent.setData(Uri.parse("package:" + packageName));
}
context.startActivity(intent);

and in manifest :

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

After that user can do whitelist your app.

Mangesh Sambare
  • 564
  • 2
  • 20
1

I had the same problem: "java.net.UnknownHostException: Unable to resolve host "host_name": No address associated with hostname". Also Internet was available with all granted Android permissions (tested them all even at runtime).

But the solution was different. The reason was that our API-host "host_name" (e.g. http://xxx.yyyy.zz) was in LAN and was not able from outer network. The same issue may be caught if you are calling your company's outer "host_name" from company's LAN (it seems like "DNS Rebind attack" for server). To test it you should try to open used url in a browser when the device is (and when isn't) connected to Internet from a local LAN (e.g. company's Wi-Fi) and check if server response is correct.

The @Mangesh Sambare's problem was solved as he said above, but maybe this experience'll be helpful for somebody who're in the same situation as I was.

0

When the app goes into background mode, you need to wake up the app every now and then to sniff the mobile phone’s position. Naturally, the more often it would sniff, the faster and more reliably it would be able to detect the geofence. https://proximi.io/will-my-geofencing-function-in-the-background

Uddhav Gautam
  • 6,052
  • 3
  • 39
  • 54