android / location-samples

Multiple samples showing the best practices in location APIs on Android.
Apache License 2.0
2.71k stars 2.78k forks source link

Geofencing Sample deprecated. Please update with the best solution #174

Open pramodit opened 5 years ago

pramodit commented 5 years ago

Geofencing Sample doenst seems to be working with Android Oreo devices as per the background execution limits . My GeofenceIntentService gets killed at a later time unknowingly and i get a log as GeofenceHelper : removeGeofences - which i havent done explicitly in my app . I want my fences live forever in the app until the user uninstalls my app.

For now am using JobScheduler API to schedule it every 5 secs to get the transition triggers. Can anyone at Google Please assist me with this.

Thanks in advance.

ergun-p commented 5 years ago

+1

DevEddy commented 5 years ago

+1

janbrodhaecker commented 5 years ago

Any updates on this one?

pramodit commented 5 years ago

@janbrodhaecker Hey You could probably use the solution which am using for now. It worked for me like a charm.

janbrodhaecker commented 5 years ago

@pramodit Thanks a lot for your feedback.

Could you be so kind and provide some snippets? I read a lot about this issue and I also tried to schedule a Job - but I am not quite sure what to do when the Job is executed? Just request another location update? Is there any chance to trigger the API, so that the Geofences are evaluated again?

Thanks in advance!

pramodit commented 5 years ago

Sure @janbrodhaecker This is how i am doing it . I use this JobService and reschedule it every 1 secs in Android O to make sure the OS dont kill my service. /**

JobService for Geofences above Android Oreo @author Pramod @SInCE 16-Sept-2018. */ @TargetApi(21) public class OreoJobService extends JobService {

private JobParameters mParams;

//Assuming it takes maximum 5 seconds for private String TAG = "OreoJobService";

public boolean onStartJob(JobParameters params) {

this.mParams = params;

String command = params.getExtras().getString("command");

if (command != null && command.equals("stop")) {

    this.endJob();
    return false;
} else {

    if (MyApplication.getInstance().geoFencePendingIntent == null) {

        if (params.getExtras().containsKey("fromJobScheduler")) {

            String geoFences = MyApplication.getInstance().fetchGeoFences();

            if (geoFences != null) {

                List<Fence> fencesList = new Gson().fromJson(geoFences, new TypeToken<List<Fence>>() {
                }.getType());
                MyApplication.getInstance().createGeoFences(fencesList);

            }

        }
    } else {

        Log.e(TAG, "onStartJob: scheduling job ");
        this.scheduleJob(10000);
    }

    return true;
}

}

void endJob() { this.jobFinished(this.mParams, false); }

void scheduleJob(long interval) {

ComponentName serviceName = new ComponentName(this.getPackageName(), OreoJobService.class.getName());

PersistableBundle extras = new PersistableBundle();
extras.putString("command", "start");
extras.putInt("fromJobScheduler", 1);
JobInfo jobInfo = (new JobInfo.Builder(VariableConstants_Fortees.GEOFENCE_JOB_ID, serviceName))
        .setExtras(extras).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
        .setMinimumLatency(interval)
        .setOverrideDeadline(interval)
        .build();

JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
assert jobScheduler != null;
jobScheduler.schedule(jobInfo);
this.endJob();

}

public boolean onStopJob(JobParameters params) {

return false;

} }

###################### And here's my logic for scheduling ##########################

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

    ComponentName serviceName = new ComponentName(this, OreoJobService.class.getName());
    PersistableBundle extras = new PersistableBundle();
    extras.putString("command", "start");

    JobInfo jobInfo = (new JobInfo.Builder(VariableConstants_Fortees.GEOFENCE_JOB_ID, serviceName))
            .setExtras(extras).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
            .setMinimumLatency(1L)
            .setOverrideDeadline(1L)
            .build();

    JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);

    try {
        jobScheduler.schedule(jobInfo);
    } catch (IllegalArgumentException errorMessage) {
        errorMessage.printStackTrace();
    }

}
janbrodhaecker commented 5 years ago

@pramodit - thanks a lot for your answer. Could you just explain what you are doing, when the job is executed? Maybe you can give me some more hints for these lines:

if (MyApplication.getInstance().geoFencePendingIntent == null) {
 if (params.getExtras().containsKey("fromJobScheduler")) {
  String geoFences = MyApplication.getInstance().fetchGeoFences();
   if (geoFences != null) {
    List<Fence> fencesList = new Gson().fromJson(geoFences, new TypeToken<List<Fence>>() {}.getType());
    MyApplication.getInstance().createGeoFences(fencesList);
   }
 }

Best regards, Jan.

pramodit commented 5 years ago

Hey @janbrodhaecker , I am just creating the Geofences and adding to the GeofencingPendingIntent to trigger those transition events .

Here is the snippet for my createGeofences method -

public void createGeoFences(List fencesList){

    for (int i = 0; i < fencesList.size(); i++) {

        String type = fencesList.get(i).getType();
        if (type != null && !"".equals(type)) {

            if ("circle".equals(type)) {

                Shape shape = fencesList.get(i).getShape();
                String GEOFENCE_REQ_ID = fencesList.get(i).getName();

// mGeoList.add(GEOFENCE_REQ_ID); // String GEOFENCE_IDS = fencesList.get(i).getId(); // mGeoIdList.add(GEOFENCE_IDS);

                Foreground.fencesMap.put(fencesList.get(i).getName(), fencesList.get(i).getId());

                if (shape != null) {

                    double radius = shape.getRadius();
                    double latitude = 0.0f, longitude = 0.0f;
                    Center center = shape.getCenter();
                    if (center != null) {
                        latitude = center.getLatitude();
                        longitude = center.getLongitude();
                    }
                    LatLng latLng = new LatLng(latitude, longitude);
                    float radius_float = ((float) radius);
                    Geofence geofence = geoFenceUtils.createGeofence(GEOFENCE_REQ_ID, latLng, radius_float);
                    GeofencingRequest geofenceRequest = geoFenceUtils.createGeofenceRequest(geofence);
                    if (ContextCompat.checkSelfPermission(mInstance, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {

                        addGeoFences(geofenceRequest);

                    }
                }

            } else if ("polygon".equals(type)) {

                Shape shape = fencesList.get(i).getShape();
                String GEOFENCE_POLYGON_REQ_ID = fencesList.get(i).getName();

// mGeoList.add(GEOFENCE_POLYGON_REQ_ID); // // String GEOFENCE_POLYGON_ID = fencesList.get(i).getId(); // mGeoIdList.add(GEOFENCE_POLYGON_ID);

                Foreground.fencesMap.put(fencesList.get(i).getName(), fencesList.get(i).getId());

                ArrayList<LatLng> newLatLngList = new ArrayList<LatLng>();

                if (shape != null) {

                    List<Point> pointsList = shape.getPoints();
                    if (pointsList.size() > 0) {

                        for (int j = 0; j < pointsList.size(); j++) {
                            double latitude = pointsList.get(j).getLatitude();
                            double longitude = pointsList.get(j).getLongitude();
                            LatLng latLng = new LatLng(latitude, longitude);
                            newLatLngList.add(latLng);
                        }
                        LatLng centerPointLatLng = geoFenceUtils.getPolygonCenterPoint(newLatLngList);
                        double center_lat = centerPointLatLng.latitude;
                        double center_long = centerPointLatLng.longitude;

                        List<Float> distanceList = new ArrayList<>();
                        float res[] = new float[3];
                        for (int m = 0; m < newLatLngList.size(); m++) {
                            LatLng latLng = newLatLngList.get(m);
                            double lat = latLng.latitude;
                            double longitude = latLng.longitude;
                            Location.distanceBetween(center_lat, center_long, lat, longitude, res);
                            if (res.length > 0) {
                                for (float f : res) {
                                    distanceList.add(f);
                                }
                            }
                        }

                        Float max_dist = Collections.max(distanceList);

                        Geofence geofence = geoFenceUtils.createGeofence(GEOFENCE_POLYGON_REQ_ID, centerPointLatLng, max_dist);
                        GeofencingRequest geofenceRequest = geoFenceUtils.createGeofenceRequest(geofence);

                        if (ContextCompat.checkSelfPermission(mInstance, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {

                            addGeoFences(geofenceRequest);

                        }

                    }
                }
            }
        }
    }

}
janbrodhaecker commented 5 years ago

@pramodit - thanks for your response, I really appreciate your support!

I don't actually get the point. You are asking each second, whether the GeofencePendingIntent still exists - if not you will recreate all the registered Geofences? I thought we need to poll the current Location in the background or anything? I think just recreating them in the background will not actually help for a long term perspective - or I am still not getting it right ...

Thanks a lot! Kind regards, Jan.

ergun-p commented 5 years ago

Manage to get this working with the example. Turns out my issue was the lat and long were passed incorrectly. I was passing long then lat, when it should of been lat then long.

janbrodhaecker commented 5 years ago

Sadly I have still no idea why it should help to register the geofences within the JobService ...

ergun-p commented 5 years ago

Sadly I have still no idea why it should help to register the geofences within the JobService ...

In the example it uses JobIntentService, with a BroadcastReceiver. I think if you just use IntentService, maybe this might help you https://www.raywenderlich.com/7372-geofencing-api-tutorial-for-android

janbrodhaecker commented 5 years ago

@pramodit thanks for your response. I guess it's clear, why we need to use the BroadcastReceiver. The thing is, if I use the example provided in this project with a BroadcastReceiver - the Geofences are not triggered when the app is killed or in the background (Android Pie, Pixel 1). That's why @pramodit provided an example, where the Geofences are registered within a JobIntentService. And I don't understand why we need to use the JobIntentService to register the Geofences at all.

pramodit commented 5 years ago

@janbrodhaecker Sorry was busy and couldn't respond you ASAP. There's no requirement of polling your current lat long here . There was something called GeofenceHelper which was checking for the GeofenceTransitions internally with the current lat long. So i'll register the fences to the GeofencingAPI only once by that GeofencingPendingIntent and thats the reason why i do have the check if the pendingIntent is null or not . if its null only then i do re-register them . else am just scheduling the job to make sure the services doesnt get killed.

pramodit commented 5 years ago

I hope its clear now. @ergun-p @janbrodhaecker

janbrodhaecker commented 5 years ago

@pramodit - thanks a lot for your response again! Just one last question: How are you creating the PendingIntent for the Geofences - via .getService or via .getBroadcast ? Really looking forward to try out your approach! Is ist still working fine on your devices? Are you also testing on Android Pie?

ergun-p commented 5 years ago

I hope its clear now. @ergun-p

@pramodit Thanks, but my issue was different and is resolved.

pramodit commented 5 years ago

@janbrodhaecker Yes its working on Android Pie and Android O as well. I used PendingIntent.getBroadcast for the Geofences Creation.

janbrodhaecker commented 5 years ago

@pramodit thanks a lot! I will give it a try!

mcanonic commented 5 years ago

Hi have a basic question on this App: it does not show anything. When I click on "add geofences" nothing happen. Do you guys use the code as is, or you make some change?

pramodit commented 5 years ago

You could use the code from this thread as a workaround solution for the add the geofences which keeps running in Android O and Android Pie. And from this example. it shows a notification when you enter into the geofence region. Please ensure you have changed the radius and lat longs as per your requirement according to your location in the example. @mcanonic

mcanonic commented 5 years ago

ok, I see many different contributions and some link to external resource code. Is there a place where I can find the complete source code working? Thanks

secureCam2018 commented 5 years ago

Anyone tried with work manager - beta?

I tried work manager on Android P, seems fine to me so far. Hopefully, it will stays that way. fingers crossed My geofences will expires after 24 hours, which i will register the geofences again at the 23rd hour by removing all geofences and register again.

janbrodhaecker commented 5 years ago

@secureCam2018 can you describe what you want to achieve with the work manager? Or what you've implemented in detail?

secureCam2018 commented 5 years ago

@janbrodhaecker,

I used workmanager with a one time work request to register Geofences.

And instead of jobIntentService, I used workmanager to handle the triggering events. (After broadcast receiver received the events)

So a total of 2 workmanagers is used.

I think using one workmanager would suffice too, changing the jobIntentService to workmanager. But have not had the chance to try it out yet.

Hope this clears your doubt