7

I am trying to test GeoFences for a location aware application. I have gotten as many as 11 consecutive successes, but that is typically followed by dozens of failures. Rebooting the device does not help. Uninstalling/reinstalling does not help. Disabling location, re-enabling location does not help. Changing the devices Location Accuracy settings does not help.

I've tested on v2.3.4, v4.0.3, v4.4.2, v4.4.4 and 2 v5.0.1 physical devices. One of the Lollipop devices is a device with cellular service. The remaining devices are SIMless and used for testing only. Side note: the devices without service struggle with setting their location via MockLocation, but if they manage to set their location they almost never register a fence entry/exit.

Location is being updated via Mock Location well enough, but the GeoFence(s) are not being triggered with any reliability.

I've Googled it. I've looked at the documentation on developer.android.com site. I've searched StackOverflow. In fact, many of the suggestions made by others have been implemented in the code below.

So, how can I reliably trigger my GeoFence using MockLocation?

import android.location.Location;
import android.location.LocationManager;

public class GeoFenceTester extends ActivityInstrumentationTestCase2<GeoFenceTesterActivity> {


    public static final String TAG = GeoFenceTester.class.getSimpleName();

    private LocationManager locationManager;
    private Activity activityUnderTest;

    protected void setUp() throws Exception {
        super.setUp();
        activityUnderTest = getActivity();
        /*
            MOCK Locations are required for these tests.  If the user has not enabled MOCK Locations
            on the device or emulator these tests cannot work.
         */
        if (Settings.Secure.getInt(activityUnderTest.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 0) {
            throw new RuntimeException("Mock locations are currently disabled in Settings - These tests require mock locations");
        }

        Log.i(TAG, "Setup MOCK Location Providers");
        locationManager = (LocationManager) activityUnderTest.getSystemService(Context.LOCATION_SERVICE);

        Log.i(TAG, "GPS Provider");
        locationManager.addTestProvider(LocationManager.GPS_PROVIDER, false, true, false, false, false, false, false, Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
        locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);

        Log.i(TAG, "Network Provider");
        locationManager.addTestProvider(LocationManager.NETWORK_PROVIDER, true, false, true, false, false, false, false, Criteria.POWER_MEDIUM, Criteria.ACCURACY_FINE);
        locationManager.setTestProviderEnabled(LocationManager.NETWORK_PROVIDER, true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.wtf(TAG, String.format("Location Accuracy: %1$d", Settings.Secure.getInt(activityUnderTest.getContentResolver(), Settings.Secure.LOCATION_MODE)));
        }

        /* Our EventBus for onEvent() callbacks */
        EventBus.getDefault().register(this);
    }

    protected void tearDown() {
        /* Our EventBus for onEvent() callbacks */
        EventBus.getDefault().unregister(this);

        locationManager.removeTestProvider(LocationManager.GPS_PROVIDER);
        locationManager.removeTestProvider(LocationManager.NETWORK_PROVIDER);
        locationManager = null;

        activityUnderTest = null;

        super.tearDown();
    }

    public void testGeoFence() {

        /*
            Using one or the other or both of these makes no difference.
        */
        Location mockGpsLocation = new Location(LocationManager.GPS_PROVIDER);
        mockGpsLocation.setLatitude(32.652411);
        mockGpsLocation.setLongitude(-79.938063);
        mockGpsLocation.setAccuracy(1.0f);
        mockGpsLocation.setTime(System.currentTimeMillis());

        Location mockNetworkLocation = new Location(LocationManager.NETWORK_PROVIDER);
        mockNetworkLocation.setLatitude(32.652411);
        mockNetworkLocation.setLongitude(-79.938063);
        mockNetworkLocation.setAccuracy(1.0f);
        mockNetworkLocation.setTime(System.currentTimeMillis());

        /*
            setElapsedRealtimeNanos() was added in API 17
         */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            mockGpsLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
            mockNetworkLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
        }

        try {
            Method locationJellyBeanFixMethod = Location.class.getMethod("makeComplete");
            if (locationJellyBeanFixMethod != null) {
                locationJellyBeanFixMethod.invoke(mockGpsLocation);
                locationJellyBeanFixMethod.invoke(mockNetworkLocation);
            }
        } catch (Exception e) {
            // There's no action to take here.  This is a fix for Jelly Bean and no reason to report a failure.
        }

        for (int i = 0; i < 30; i++){
            Log.i(TAG, String.format("Iterating over our location ... (%1$d)", i));
            locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mockGpsLocation);
            locationManager.setTestProviderLocation(LocationManager.NETWORK_PROVIDER, mockNetworkLocation);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage(), e);
            }
        }
    }

    public void onEvent(final GeoFenceUpdateEvent event) {
        // This doesn't get called about 9/10 times.
        // I have a GeoFence with a 5000m radius around 32.652411, -79.938063
    }

    public void onEvent(final LocationUpdateEvent event) {
        // This gets called without incident and event.getLatitude() & event.getLongitude() are correct.
    }

}
Bill Mote
  • 12,644
  • 7
  • 58
  • 82
  • Have you tried testing by starting the mock locations outside of the fence and then entering/exiting at a pace which would map to walking/biking/driving? – Morrison Chang Jan 29 '15 at 17:32
  • The starting location is definitely outside of the fenced area. I apply location updates every 1000ms. The location is being successfully updated, however, the fence rarely triggers its entry message. I have logging that I excluded for readability. My starting location is typically 89000+ meters from the desired location. After about iteration 3 I get a location update event and my distance from my desired location is ~0.2m (rounding). – Bill Mote Jan 29 '15 at 17:34
  • So you are traveling at spacecraft speeds or am I reading your last comment wrong. I would think internally there would be some limits to prevent noisy data. Also can you clarify if you are using AOSP Location Manager or Google Services. – Morrison Chang Jan 29 '15 at 17:39
  • AOSP (question updated) as for my rate of travel: it does not matter. I'm outside the fence and then I'm inside the fence. – Bill Mote Jan 29 '15 at 18:27
  • More of a side note since you are testing with AOSP - when I was doing testing using Google Services, I found that if I limited my mock location update to something 'reasonable' and let the results settle to account for 'noise' I was able to get consistent results, but my testing scenario wasn't the same as yours (5-10 seconds/update, separate app providing mock, Google Services) so perhaps someone else has a thought. – Morrison Chang Jan 29 '15 at 18:41
  • Much appreciated. I think I'm onto the solution. I'll update here if so. – Bill Mote Jan 29 '15 at 18:42

1 Answers1

5

The Geofence manager will consider all system-wide location requests equally, not just those made by your specific application. If you have any other applications or services on the device that are currently using a Location Services, these external applications be producing Lat/Lon locations used in consideration when triggering your Geofences.

Given you are producing Mock Locations, it is possible that external applications or services are producing conflicting LocationUpdates, and that these jumps between Mock and Real locations are causing your unstable Geofence test results.

You may be able to confirm this by disabling your real Location Services in settings and purely using Mock Locations, or look for any applications that may be currently using Location Services and re-testing once those specific apps have been stopped or disabled.

phoenixillusion
  • 621
  • 5
  • 6
  • This is, in fact, exactly what was happening. I removed a couple of applications that used location services and I force stopped those that I wanted to keep. All tests have been successful since. Thank you for the help!!! – Bill Mote Jan 29 '15 at 19:15
  • 3
    Disabling location services entirely, prevents mock locations from triggering in my app. – IgorGanapolsky Apr 06 '15 at 19:17