Search code examples
androidgpsgeolocationproximity

Proximity Alerts false positives


I set up proximity alerts in my app and it works well to catch where I am. However, I noticed that I am receiving lots of alerts when I shouldn't be and I can't figure out why.

The frequency of false positives is inversely proportional to how far I am from the prox alert. If I set my prox to 5km away, I'll get false positives every other hour, but if I set it to Northern Canada, I will not get false positives.

Here is what my log looks like from 5km away (my range is set to 100m)

12:25:08 (exited) //This is correct, as I set my mock location away
3:11:45 (enter) //This and the following are all incorrect
3:12:31 (exit)
3:15:45 (enter)
3:15:45 (exit)
3:20:50 (enter)
3:21:10 (exit) 
7:43:13 (enter)
7:43:24 (exit)
8:45:21 (enter)
8:45:49 (exit)
8:54:23 (enter)
8:55:09 (exit)
10:26:07 (enter)
10:26:13 (exit)
10:35:03 (enter)
10:35:14 (exit)
2:00:13 (enter) //I sleep, so phone is not being used from here to end of logs
2:00:32 (exit)
2:04:14 (enter)
2:04:52 (exit)
2:18:16 (enter)
2:19:33 (exit)
2:32:20 (enter)
2:33:10 (exit) 
4:56:15 (enter)
4:56:25 (exit)
6:03:42 (enter)
6:04:04 (exit)
6:56:57 (enter)
6:57:08 (exit)

Can anyone see a pattern or suspect why I am getting false positives? (see bottom for more possible clues)

My natural suspicion is that Prox Alerts are not very accurate, but I have found no evidence of other people complaining about this. Also, other prox alert apps (like Road Rooster) are not malfunctioning on my device.

//This is called once when when initial location is selected, the code for which I have ommitted. 
public void addProximityAlert(double lat, double lng){
    LocationManager lm=(LocationManager) getSystemService(LOCATION_SERVICE);
    SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREFS, Context.MODE_MULTI_PROCESS);
    removeAllProximityAlerts(this);
    float range = (float) prefs.getInt("range", -1);
    if (range < 0){
        prefs.edit().putFloat("range", (float) 50);
        range = 50;
    }
    Intent intent = new Intent(Constants.PROX_INTENT_FILTER);
    int alertId= (int) System.currentTimeMillis();
    PendingIntent pi = PendingIntent.getBroadcast(this, alertId , intent, 0);
    SharedPreferences.Editor editor = prefs.edit();
    editor.putInt("maxAlertId", alertId).commit();
    Util.putDouble(editor, Constants.DEST_LAT, lat);
    Util.putDouble(editor, Constants.DEST_LNG, lng);
    editor.commit();
    lm.addProximityAlert(lat, lng, range, -1, pi);
}

public void removeAllProximityAlerts(Context context) {
    LocationManager lm=(LocationManager) getSystemService(LOCATION_SERVICE);
    SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREFS, Context.MODE_MULTI_PROCESS);
    int maxAlertId = prefs.getInt("maxAlertId", 0);      

    Intent intent = new Intent(Constants.PROX_INTENT_FILTER);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this , maxAlertId, intent, 0);
    lm.removeProximityAlert(pendingIntent);
}

Receiver

public class ProximityReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent arg1) {

    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
    wl.acquire();

    String k = LocationManager.KEY_PROXIMITY_ENTERING;
    boolean state = arg1.getBooleanExtra(k, false);

    String enterOrLeave;
    if (state) {
        //Todo: Enter state
        enterOrLeave="entered";
        updateLastDayRecord("Went to gym");
    } else {
        //Todo: Exit state
        enterOrLeave="exited";
    }
    //Do stuff (ommitted unrelated code)
 }
}

Manifest

<?xml version="1.0" encoding="utf-8"?>

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

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:name="GlobalState"
    android:theme="@style/AppTheme" >
    <activity
        android:name=".AllinOneActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".IntroductionActivity"
        android:label="@string/intro_label"
        android:parentActivityName=".AllinOneActivity" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value=".AllinOneActivity" />
    </activity>

    <activity android:name=".MessageBodyActivity"
        android:label="@string/message_label"
        android:parentActivityName=".AllinOneActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value=".AllinOneActivity" />
    </activity>


    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="@string/GOOGLE_API_KEY" />

    <receiver
        android:name=".AlarmManagerBroadcastReceiver"
        android:process=":remote">
    </receiver>

    <receiver
        android:name=".ProximityReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.pipit.agc.agc.ProximityReceiver" />
        </intent-filter>
    </receiver>

    <receiver android:name=".BootReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>
</application>

The bootreceiver just resets the alarm manager.

Additional info

  • Tested on Nexus 5
  • Always receives prox alerts when expected
  • Frequency of false positives is higher when closer
  • I'm checking LocationManager's getLastKnownLocation in my receiver and it is never being updated, though I could be using it incorrectly
  • Problem persists when switching to explicit intents (using registereceiver)
  • Problem persists even after a clean install with only one position chosen (implying that multiple conflicting alerts is not the problem)

Spent two weekends trying to figure out this bug - I keep thinking it's been fixed and a few hours later I'll get another false positive.


Solution

  • Proximity alerts are GPS or network location based. Both of these have inaccuracy. In fact, the accuracy you're passed back in the Location object is in meters. There's only a 67% chance you're in that range- there's a 1/3 chance you're outside of it.

    Slight inaccuracies is far more likely than large ones. Being off by 10m with GPS? Reasonable, in fact common. Being off by 1km? Could happen if something weird occured with a sattelite system, but should quickly correct itself. Being off by 100 km? Isn't going to happen, outside of a bug in the software.

    So lets say that the chances of a GPS location being off by 5km is 5 percent. You get 2 location updated per minute, or 120 per hour. The chance of not having a false positive is .95^120, or .2 percent. Even if there's only a 1 percent chance of a false positive, that's a 27% chance of having an hour without one. False positives are just something you have to deal with when doing location based algorithms.

    One way to fix this is not to fire your action off of proximity alerts. Instead, when the proximity alert fires request location updates and make sure that it stays within the area for an update or two. That will add a delay, but make you have fewer false positives. Also note that as you get closer to the target areas, false positives will increase as the amount of inaccuracy needed to put you in the area decreases, so it becomes more likely.