TimothyP
TimothyP

Reputation: 21765

Mocking a location using LocationManager.SetProviderLocation, how can I make the Location complete

I'm trying to mock my location by creating a Location instance and then using the LocationManager.SetTestProviderLocation(string providerName, Locatin location) method.

For example:

Location location = new Location(providerName)
{
    Latitude = 51.20958,
    Longitude = 2.92372,
    Accuracy = 100.0f,
    Time = DateTime.Now.Ticks,
    Altitude = 1.0,
    Speed = 0.0f,
    Bearing = 0.0f,
    Provider = providerName,
};


locationManager.SetTestProviderLocation(providerName, location);

This always results in an exception:

"Incomplete location object, missing timestamp or accuracy? Location[gps 51.209580,2.923720 acc=1 et=?!? alt=1.0 vel=0.0 bear=0.0]"

I found some information on the subject here.
The author mentions that the LocationManger checks an isComplete (method)
to determine whether or not the Location is complete.

I expected the mono version of the class to have an IsComplete property
and MakeComplete method, but they do not exist (or at least I could not find them)

Taking a closer look at the Android SDK source code:

public void makeComplete() 
{
        if (mProvider == null) mProvider = "?";
        if (!mHasAccuracy) {
            mHasAccuracy = true;
            mAccuracy = 100.0f;
        }
        if (mTime == 0) mTime = System.currentTimeMillis();
        if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
}

I'm guessing that the "et=?!?" value in the exception refers to the mElapsedRealtimeNanos field
and since there is no way that I know off to access that field I cannot get mocking to work.

Am I missing something, is this a missing feature of Mono for Android or
is there perhaps another way to make it work.

Note: I'm aware there are ways of setting a fake location using DDMS, but I'm looking for a way to automate the location mocking

UPDATE

Some time after posting this question I realized I wasn't targeting API level 16.
As a result Mono for Android did not expose the ElapsedRealtimeNanos property.

After switching to API level 16 the property became available. It did not however,
provide a MakeComplete method. (Perhaps this is a bug / missing feature).

So I made a quick solution to verify the behavior:

Location location = new Location(providerName)
{
    Longitude = locations[position, 0],
    Latitude = locations[position, 1],                    
    Accuracy = 100.0f,
    Time = DateTime.Now.Ticks,
    Altitude = 1.0,
    Speed = 0.0f,
    Bearing = 0.0f,
    Provider = providerName,
    ElapsedRealtimeNanos = (long)(TimeSpan.FromTicks(DateTime.Now.Ticks)
                                          .TotalMilliseconds * 1000000)
};

While this solves the problem for Jelly Bean devices (4.1 or later) the code
will break on older devices. Which is where the solution suggested by @Cheesebaron comes into play.

Upvotes: 4

Views: 1740

Answers (1)

Cheesebaron
Cheesebaron

Reputation: 24470

You will probably need to access makeComplete through JNI with code like:

public class MyLocation : Location
{
    protected MyLocation(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
    }

    public MyLocation(Location l) : base(l)
    {
    }

    public MyLocation(string provider) : base(provider)
    {
    }

    public void MakeComplete()
    {
        if ((int)Build.VERSION.SdkInt >= 16)
        {
            var locationClass = JNIEnv.FindClass("android/location/Location");
            var makeComplete = JNIEnv.GetMethodID(locationClass, "makeComplete", "()V");
            JNIEnv.CallVoidMethod(Handle, makeComplete);
        }
    }
}

Then simply use instances of MyLocation where you call MakeComplete. However it will only run on API 16.

Upvotes: 3

Related Questions