How to define geographical boundaries via Geofencing in Android Android 06.06.2017

How to define geographical boundaries via Geofencing in Android

Geofencing is a feature in a software program that uses the global positioning system to define geographical boundaries. Combining a user position with a geofence perimeter, it is possible to know if the user is inside or outside the geofence or even if he is exiting or entering the area.

Geofence API is part of Google's Location APIs. It includes Geofence, GeofencingRequest, GeofenceApi, GeofencingEvent, and GeofenceStatusCodes.

Geofence is an interface that represents a geographical area that should be monitored. It is created by using the Geofence.Builder. During its creation, you set the monitored region, the geofence's expiration date, responsiveness, an identifier, and the kind of transitions that it should be looking for.

Geofence geofence = new Geofence.Builder()
    // id = UUID.randomUUID().toString();
    .setRequestId(GEOFENCE_REQ_ID) // Geofence ID
    // defining fence region
    .setCircularRegion(LATITUDE, LONGITUDE, RADIUS)
    //.setExpirationDuration(Geofence.NEVER_EXPIRE)
    .setExpirationDuration(DURANTION) // expiring date
    // transition types that it should look for
    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | 
        Geofence.GEOFENCE_TRANSITION_EXIT)
    .build();

There are several geofence transitions

  • GEOFENCE_TRANSITION_ENTER indicates when the user enters the monitored region.
  • GEOFENCE_TRANSITION_EXIT indicates when the user exits the region.
  • GEOFENCE_TRANSITION_DWELL indicates that the user entered the area and spent some time there. It is useful to avoid multiple alerts when the user is entering and exiting the area too fast. You can configure the dwelling time using the setLoiteringDelay parameter.

The GeofencingRequest class receives the geofences that should be monitored. You can create an instance by using a Builder, passing a Geofence or a List<Geofence>, and the kind of notification to trigger when the geofence(s) is created.

GeofencingRequest request = new GeofencingRequest.Builder()
    // notification to trigger when the Geofence is created
    .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
    // add a Geofence
    .addGeofence(geofence)
    .build();

The GeofencingApi class is the entry point for all interactions with Google's geofencing API. It is part of the Location APIs and it depends on a GoogleApiClient to work. You will use the GeofencingApi to add and remove geofences.

To add a geofence, you call the addGeofence() method. It monitors the given area using the settings passed to the GeofencingRequest and shoots a PendingIntent when a geofence transition, entering or exiting the area, takes place.

PendingResult<Status> addGeofences (GoogleApiClient client,
    GeofencingRequest geofencingRequest,
    PendingIntent pendingIntent)

To remove the geofence, you call removeGeofences(). You can either remove the geofence using its request identifier or its pending intent.

PendingResult<Status> removeGeofences(GoogleApiClient client, List<String> geofenceRequestIds);
PendingResult<Status> removeGeofences (GoogleApiClient client, PendingIntent pendingIntent);

In this tutorial, we create a simple application that monitors the user location and posts a notification when the user enters or exits a geofenced area. The app consists of only one Activity and an IntentService. We also take a quick look at GoogleMap, GoogleApiClient, and FusedLocationProviderApi, and we explore some caveats of the geofence API.

We need to set the correct permissions to create and use geofences. Add the following permission to the project's manifest:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission. ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

Also, geofencing needs Google Services API. Open your build.gradle and add the dependency.

compile 'com.google.android.gms:play-services-location:10.0.1'

First we have to check whether this device has Google Play Services installed.

int resp = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resp == ConnectionResult.SUCCESS) {
    Log.e(TAG, "Your device support Google Play Services.");
} else {
    Log.e(TAG, "Your device doesn't support Google Play Services.");
}

Our project will consist of one layout, the MainActity layout. It contains the device's current latitude and longitude, and a GoogleMap fragment that displays the geofences and the user's position.

Following is activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="@color/colorPrimaryDark"
        android:paddingTop="5dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:paddingBottom="5dp">

        <TextView
            android:id="@+id/lat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:layout_weight="0.4"
            android:text="Lat: " />

        <TextView
            android:id="@+id/lon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:layout_weight="0.4"
            android:text="Long: " />

        <Button
            android:id="@+id/btnStart"
            android:text="Start"
            android:onClick="runGeofence"
            android:layout_weight="0.2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:name="com.google.android.gms.maps.MapFragment"
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        map:uiZoomControls="true"/>
</LinearLayout>

Since we are using a MapFragment, we need to set up and initialize a GoogleMap instance. First, you need to obtain an API key. Once you have an API key, add it to the project's manifest.

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="YOUR_API_KEY"/>

Following is steps that describe logic of our application. After that you can see full code.

  1. Let's begin with the GoogleMap instance. Implement GoogleMap.OnMapReadyCallback, GoogleMap.OnMapClickListener, and GoogleMap.OnMarkerClickListener in the Activity class and initialize the map.
  2. To use the GeofencingApi interface, we need a GoogleApiClient entry point. It will responsible for GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener callbaks.
  3. We also need to access the user's current location. The FusedLocationProviderApi interface gives us this information and allows a great level of control of the location request. This is very important, considering that location requests have a direct effect over the device's battery consumption. Next step is implementing a LocationListener. Check if the user gave the application the appropriate permissions by creating the Location request and display their current location on the screen. It is important to address that created LocationRequest isn't optimized for a production environment. The UPDATE_INTERVAL is too short and would consume too much battery power. A more realistic configuration for production could be: UPDATE_INTERVAL = 3 * 60 * 1000, FASTEST_INTERVAL = 30 * 1000.
  4. Our Activity needs two different markers. A locationMarker uses the latitude and longitude given by the FusedLocationProviderApi to inform the device's current location. A geoFenceMarker is the target for the geofence creation as it uses the last touch given on the map to retrieve its position.
  5. At last, it is time to create a geofence and create the GeofencingRequest object. We use the geoFenceMarker as the center point for the geofence. We use a PendingIntent object to call a IntentService that will handle the GeofenceEvent.
  6. The startGeofence() method is responsible for starting the geofencing process in the MainActivity class.
import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.api.ResultCallback;

public class LocatonActivity extends AppCompatActivity  implements
        LocationListener,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        OnMapReadyCallback,
        GoogleMap.OnMapClickListener,
        GoogleMap.OnMarkerClickListener,
        ResultCallback<Status>
        {

    private static final String TAG = LocatonActivity.class.getSimpleName();

    private TextView textLat, textLong;
    private MapFragment mapFragment;
    private GoogleMap map;
    private Location lastLocation;
    private LocationRequest locationRequest;
    // defined in mili seconds
    // this number in extremely low, and should be used only for debug
    private final int UPDATE_INTERVAL =  1000;
    private final int FASTEST_INTERVAL = 900;
    private GoogleApiClient googleApiClient;
    private Marker locationMarker;
    private Marker geoFenceMarker;
    private PendingIntent geoFencePendingIntent;
    private static final long GEO_DURATION = 60 * 60 * 1000;
    private static final String GEOFENCE_REQ_ID = "My Geofence";
    private static final float GEOFENCE_RADIUS = 500.0f; // in meters
    private final int GEOFENCE_REQ_CODE = 0;
    private Circle geoFenceLimits;
    private final int REQ_PERMISSION = 777;
    private static final String NOTIFICATION_MSG = "NOTIFICATION MSG";

    // create a Intent send by the notification
    public static Intent makeNotificationIntent(Context context, String msg) {
        Intent intent = new Intent(context, LocatonActivity.class);
        intent.putExtra(NOTIFICATION_MSG, msg);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_locaton);
        textLat = (TextView) findViewById(R.id.lat);
        textLong = (TextView) findViewById(R.id.lon);

        checkGooglePlayServicesAvailable();
        checkGPS();

        // initialize GoogleMaps
        initGMaps();
        createGoogleApi();
    }

    // initialize GoogleMaps
    private void initGMaps(){
        mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    // callback called when Map is ready
    @Override
    public void onMapReady(GoogleMap googleMap) {
        Log.d(TAG, "onMapReady()");
        map = googleMap;
        map.setOnMapClickListener(this);
        map.setOnMarkerClickListener(this);
    }

    // callback called when Map is touched
    @Override
    public void onMapClick(LatLng latLng) {
        Log.d(TAG, "onMapClick("+latLng +")");
        markerForGeofence(latLng);
    }

    // callback called when Marker is touched
    @Override
    public boolean onMarkerClick(Marker marker) {
        Log.d(TAG, "onMarkerClickListener: " + marker.getPosition() );
        return false;
    }

    private void createGoogleApi() {
        Log.d(TAG, "createGoogleApi()");
        if (googleApiClient == null) {
            googleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();

        // call GoogleApiClient connection when starting the Activity
        googleApiClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();

        // disconnect GoogleApiClient when stopping Activity
        googleApiClient.disconnect();
    }

    // GoogleApiClient.ConnectionCallbacks connected
    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Log.d(TAG, "onConnected()");
        getLastKnownLocation();
    }

    // GoogleApiClient.ConnectionCallbacks suspended
    @Override
    public void onConnectionSuspended(int i) {
        Log.d(TAG, "onConnectionSuspended()");
    }

    // GoogleApiClient.OnConnectionFailedListener fail
    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.d(TAG, "onConnectionFailed()");
    }

    // get last known location
    private void getLastKnownLocation() {
        Log.d(TAG, "getLastKnownLocation()");
        if (checkPermission()) {
            lastLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
            if (lastLocation != null) {
                Log.d(TAG, "LasKnown location. " +
                        "Long: " + lastLocation.getLongitude() +
                        " | Lat: " + lastLocation.getLatitude());
                writeLastLocation();
                startLocationUpdates();
            } else {
                Log.d(TAG, "No location retrieved yet");
                startLocationUpdates();
            }
        }
        else askPermission();
    }

    // start location updates
    private void startLocationUpdates(){
        Log.d(TAG, "startLocationUpdates()");
        locationRequest = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(UPDATE_INTERVAL)
                .setFastestInterval(FASTEST_INTERVAL);

        if (checkPermission())
            LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this);
    }

    @Override
    public void onLocationChanged(Location location) {
        Log.d(TAG, "onLocationChanged [" + location + "]");
        lastLocation = location;
        writeActualLocation(location);
    }

    // write location coordinates on UI
    private void writeActualLocation(Location location) {
        textLat.setText("Lat: " + location.getLatitude());
        textLong.setText("Long: " + location.getLongitude());
        markerLocation(new LatLng(location.getLatitude(), location.getLongitude()));
    }

    private void writeLastLocation() {
        writeActualLocation(lastLocation);
    }

    // check for permission to access Location
    private boolean checkPermission() {
        Log.d(TAG, "checkPermission()");
        // ask for permission if it wasn't granted yet
        return (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED );
    }

    // asks for permission
    private void askPermission() {
        Log.d(TAG, "askPermission()");
        ActivityCompat.requestPermissions(
                this,
                new String[] {Manifest.permission.ACCESS_FINE_LOCATION},
                REQ_PERMISSION
        );
    }

    // verify user's response of the permission requested
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 
        @NonNull int[] grantResults) {
        Log.d(TAG, "onRequestPermissionsResult()");
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch ( requestCode ) {
            case REQ_PERMISSION: {
                if ( grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED ){
                    // permission granted
                    getLastKnownLocation();

                } else {
                    // permission denied
                    permissionsDenied();
                }
                break;
            }
        }
    }

    // app cannot work without the permissions
    private void permissionsDenied() {
        Log.d(TAG, "permissionsDenied()");
    }

    // Create a Location Marker
    private void markerLocation(LatLng latLng) {
        Log.d(TAG, "markerLocation("+latLng+")");
        String title = latLng.latitude + ", " + latLng.longitude;
        MarkerOptions markerOptions = new MarkerOptions()
                .position(latLng)
                .title(title);
        if (map != null) {
            // remove the anterior marker
            if (locationMarker != null)
                locationMarker.remove();
            locationMarker = map.addMarker(markerOptions);
            float zoom = 14f;
            CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, zoom);
            map.animateCamera(cameraUpdate);
        }
    }

    // create a marker for the geofence creation
    private void markerForGeofence(LatLng latLng) {
        Log.i(TAG, "markerForGeofence("+latLng+")");
        String title = latLng.latitude + ", " + latLng.longitude;
        // define marker options
        MarkerOptions markerOptions = new MarkerOptions()
                .position(latLng)
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
                .title(title);
        if (map != null) {
            // remove last geoFenceMarker
            if (geoFenceMarker != null)
                geoFenceMarker.remove();

            geoFenceMarker = map.addMarker(markerOptions);
        }
    }

    // create a Geofence
    private Geofence createGeofence(LatLng latLng, float radius) {
        Log.d(TAG, "createGeofence");
        return new Geofence.Builder()
                .setRequestId(GEOFENCE_REQ_ID)
                .setCircularRegion(latLng.latitude, latLng.longitude, radius)
                .setExpirationDuration(GEO_DURATION)
                .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER 
                    | Geofence.GEOFENCE_TRANSITION_EXIT)
                .build();
    }

    // Create a Geofence Request
    private GeofencingRequest createGeofenceRequest( Geofence geofence ) {
        Log.d(TAG, "createGeofenceRequest");
        return new GeofencingRequest.Builder()
            .setInitialTrigger( GeofencingRequest.INITIAL_TRIGGER_ENTER )
            .addGeofence( geofence )
            .build();
    }

    private PendingIntent createGeofencePendingIntent() {
        Log.d(TAG, "createGeofencePendingIntent");
        if (geoFencePendingIntent != null)
            return geoFencePendingIntent;

        Intent intent = new Intent(this, GeofenceTrasitionService.class);
        return PendingIntent.getService(this, GEOFENCE_REQ_CODE, intent, 
            PendingIntent.FLAG_UPDATE_CURRENT);
    }

    // add the created GeofenceRequest to the device's monitoring list
    private void addGeofence(GeofencingRequest request) {
        Log.d(TAG, "addGeofence");
        if (checkPermission())
            LocationServices.GeofencingApi.addGeofences(
                    googleApiClient,
                    request,
                    createGeofencePendingIntent()
            ).setResultCallback(this);
    }

    @Override
    public void onResult(@NonNull Status status) {
        if (status.isSuccess()) {
            drawGeofence();
        } else {
            Log.d(TAG, "Registering geofence failed: " + status.getStatusMessage() + 
                " : " + status.getStatusCode());
        }
    }

    private void drawGeofence() {
        if (geoFenceLimits != null)
            geoFenceLimits.remove();

        CircleOptions circleOptions = new CircleOptions()
                .center( geoFenceMarker.getPosition())
                .strokeColor(Color.argb(50, 70,70,70))
                .fillColor( Color.argb(100, 150,150,150) )
                .radius( GEOFENCE_RADIUS );
        geoFenceLimits = map.addCircle(circleOptions);
    }

    // start Geofence creation process
    private void startGeofence() {
        Log.d(TAG, "startGeofence()");
        if( geoFenceMarker != null ) {
            Geofence geofence = createGeofence(geoFenceMarker.getPosition(), GEOFENCE_RADIUS);
            GeofencingRequest geofenceRequest = createGeofenceRequest(geofence);
            addGeofence(geofenceRequest);
        } else {
            Log.e(TAG, "Geofence marker is null");
        }
    }

    public void runGeofence(View v){
        startGeofence();
        Button btnStart = (Button) findViewById(R.id.btnStart);
        btnStart.setEnabled(false);
    }

    public int checkGooglePlayServicesAvailable() {
        int resp = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (resp == ConnectionResult.SUCCESS) {
            Log.d(TAG, "Your device support Google Play Services!");
        } else {
            Log.d(TAG, "Your device doesn't support Google Play Services.");
        }
        return resp;
    }

    public void checkGPS() {
        LocationManager locationManager = (LocationManager) getSystemService(Service.LOCATION_SERVICE);
        boolean isGPS = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        boolean isNetwork = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        Log.d(TAG, "checkGPS: GPS - " + isGPS + ". NETWORK - " + isNetwork);
    }
}

We can now finally create the GeofenceTrasitionService.class mentioned earlier. This class extends IntentService and is responsible for handling the GeofencingEvent. First, we get this event from the received intent.

GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);

We then check if the kind of geofencing transition that took place is of interest to us. If it is, we retrieve a list of the triggered geofences and create a notification with the appropriate actions.

// retrieve GeofenceTrasition
int geoFenceTransition = geofencingEvent.getGeofenceTransition();

// check if the transition type
if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
        geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
    // 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 );
}

If you want to use an IntentService to listen for geofence transitions, add an element specifying the service name to AndroidManifest.xml file. This element must be a child of the <application> element:

<application
   android:allowBackup="true">
   ...
   <service android:name=".GeofenceTrasitionService"/>
<application/>

Following is full code of IntentService.

public class GeofenceTrasitionService extends IntentService {
    private static final String TAG = GeofenceTrasitionService.class.getSimpleName();
    public static final int GEOFENCE_NOTIFICATION_ID = 0;

    public GeofenceTrasitionService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // Retrieve the Geofencing intent
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);

        // Handling errors
        if (geofencingEvent.hasError()) {
            String errorMsg = getErrorString(geofencingEvent.getErrorCode());
            Log.e(TAG, errorMsg);
            return;
        }

        // Retrieve GeofenceTrasition
        int geoFenceTransition = geofencingEvent.getGeofenceTransition();
        // Check if the transition type
        if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
                geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
            // 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());
        }

        String status = null;
        if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
            status = "Entering ";
        else if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT)
            status = "Exiting ";
        return status + TextUtils.join(", ", triggeringGeofencesList);
    }

    // Send a notification
    private void sendNotification(String msg) {
        Log.i(TAG, "sendNotification: " + msg);

        // Intent to start the main Activity
        Intent notificationIntent = MainActivity.makeNotificationIntent(
                getApplicationContext(), msg
        );

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.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_action_location)
                .setColor(Color.RED)
                .setContentTitle(msg)
                .setContentText("Geofence Notification!")
                .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.";
        }
    }
}

We can send location using telnet command. To use this, you need to connect to the device from the command line using the following command:

#telnet localhost [DEVICE_PORT]
telnet localhost 5554

The device port is shown in the virtual device window. The device port is usually equal to 5554.

It is possible that you need to authorize this connection using your auth_token, but the command line shows you where it is located. Navigate to that location and copy the token and type, auth [YOUR_AUTH_TOKEN].

You can now set the location of the device by running the following command:

# geo fix [LATITUDE] [LONGITUDE]
geo fix 49.0933625 33.4391895

Testing geofences on a physical device is best, but if need be, you can run the app on the Android emulator. Doing so requires an emulator setup with Google Play Services installed.

If you’re developing on the emulator, you may receive an error when you attempt to add a geofence. If you do, follow these steps to add location permissions to your emulator:

  1. Go to Settings > Location in your emulator.
  2. Tap on Mode on Lollipop this is near the top of the list.
  3. Set the mode to set to Device Only, then set the mode to any other option, such as High accuracy.
  4. Tap Agree on the "Use Google’s location service?"" popup.

This should remove the error you received when adding geofences in the emulator.

Result

android_geofence1.png android_geofence2.png