27

I will block a user from using my app if they fake the location. So I use isFromMockProvider to check if the location is fake (follow here). But isFromMockProvider() may return false for faked locations in some cases.

public void onLocationChanged(Location location) {
    textView.append("long:"+location.getLatitude()+" - lat:"+location.getLongitude()+" - isMock :"+location.isFromMockProvider() + "\n");
}

My case is: I use app Fake GPS location for fake to a location then I disable fake location and go to my app. Then the onLocationChanged returns the fake location with isFromMockProvider() = false

Video recorder: https://www.youtube.com/watch?v=rWVvjOCaZiI (in this video, my current location is 16.06, 108.21, the fake location is 36.26,138.28. You can see in last video the location is 36.26,138.28 but isFromMockProvider=false)

Is there any way to detect if a user uses a fake location in this case? Any help or suggestion would be great appreciated.
DEMO project

Community
  • 1
  • 1
Linh
  • 57,942
  • 23
  • 262
  • 279
  • 2
    Surely if you disable fake location then getLastLocation returns the real location and "isFromMockProvider() = false" is the correct outcome... – Nick Cardoso Mar 03 '17 at 02:11
  • 4
    @NickCardoso absolutely no, it not return real location, `getLastLocation` will return fake location. I have check it many times :( – Linh Mar 03 '17 at 02:20
  • @NickCardoso you are wrong, he already pointing that "getLastLocation return the fake location ", not real location – blackkara Mar 03 '17 at 02:21
  • What are the timestamps being returned for those fake locations that weren't really fake? – Pablo Baxter Mar 09 '17 at 20:39
  • This problem is a common issue. Can I ask your motivation for awarding bounty to an 'answer' which does not address your question? – Nick Cardoso Mar 13 '17 at 21:04
  • @NickCardoso thank you so much for pointing me. I am really sorry, I have miss a lot of things in your answer since before I read in some post like http://stackoverflow.com/a/33022821/5381331. I am sure I will maintain this question and feedback to you as soon as possible. However I still agree with the answer that I have bounty because I see user have various way for fake location (PokemonGo is example), so I also try change my business too – Linh Mar 14 '17 at 02:44
  • Well it's not something you can change once awarded anyway, but I dont understand your reply about my answer, nor the bounty answer (the philosophy sermon is nothing to do with pokemon?) – Nick Cardoso Mar 14 '17 at 09:29

4 Answers4

23

Risking a Realistic Answer

I'd like to provide an answer that helps the developer understand the public relations aspect of product design, taking the risk of criticism. Frankly, one cannot write great apps in a computer science vacuum. Satisfying user needs and balancing them with security is one of the primary issues in software interface and behavioral design today, especially in the mobile space.

From this perspective, your question, "Is there any way to detect if a user uses a fake location in this case?" may not be the most pertinent question you face. I'm not being evasive by asking this other question that may help you more and it is something I can answer well: "Is there any way to securely get the data from the user's device's geocoordinate firmware such that it cannot be spoofed?"

The answer to this one is, "No."

Android HTTP Client Contract

It is not part of the Android client-server contract or that of its competitors to guarantee user device location information.

Practical Reason

There is actually a market force that will probably push against such a guarantee indefinitely. Many device owners (and your users) want control over whether people know their true location for privacy and home and family security reasons.

Solution

The next question you can ask yourself as a designer of your software is, "How can the app or library work and provide for the needs I seek to fill with a certain percentage of the user community using today's (or tomorrow's) location spoofing software?"

If you are writing business intelligence software or there is some other statistical aspect to your system, then you need the software equivalent of error bars. If you display the stats, then the error bars would be an appropriate graphing feature. Estimating the percentage of location spoofers out of a population of users would require further study.

Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
Douglas Daseeco
  • 3,475
  • 21
  • 27
19

I use two ways to identify fake locations. First, i check mock location, like in other code here.

public static boolean isMockLocationOn(Location location, Context context) {
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
        return location.isFromMockProvider();
    } else {
        String mockLocation = "0";
        try {
            mockLocation = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return !mockLocation.equals("0");
    }
}

Second, i check running apps and services, that need permission to access mock location.

public static List<String> getListOfFakeLocationApps(Context context) {
    List<String> runningApps = getRunningApps(context);
    List<String> fakeApps = new ArrayList<>();
    for (String app : runningApps) {
        if(!isSystemPackage(context, app) && hasAppPermission(context, app, "android.permission.ACCESS_MOCK_LOCATION")){
            fakeApps.add(getApplicationName(context, app));
        }
    }
    return fakeApps;
}

public static List<String> getRunningApps(Context context, boolean includeSystem) {
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    HashSet<String> runningApps = new HashSet<>();
    try {
        List<ActivityManager.RunningAppProcessInfo> runAppsList = activityManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo processInfo : runAppsList) {
            runningApps.addAll(Arrays.asList(processInfo.pkgList));
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    try {
        //can throw securityException at api<18 (maybe need "android.permission.GET_TASKS")
        List<ActivityManager.RunningTaskInfo> runningTasks = activityManager.getRunningTasks(1000);
        for (ActivityManager.RunningTaskInfo taskInfo : runningTasks) {
            runningApps.add(taskInfo.topActivity.getPackageName());
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    try {
        List<ActivityManager.RunningServiceInfo> runningServices = activityManager.getRunningServices(1000);
        for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) {
            runningApps.add(serviceInfo.service.getPackageName());
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return new ArrayList<>(runningApps);
}

public static boolean isSystemPackage(Context context, String app){
    PackageManager packageManager = context.getPackageManager();
    try {
        PackageInfo pkgInfo = packageManager.getPackageInfo(app, 0);
        return (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return false;
}

public static boolean hasAppPermission(Context context, String app, String permission){
    PackageManager packageManager = context.getPackageManager();
    PackageInfo packageInfo;
    try {
        packageInfo = packageManager.getPackageInfo(app, PackageManager.GET_PERMISSIONS);
        if(packageInfo.requestedPermissions!= null){
            for (String requestedPermission : packageInfo.requestedPermissions) {
                if (requestedPermission.equals(permission)) {
                    return true;
                }
            }
        }
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return false;
}

public static String getApplicationName(Context context, String packageName) {
    String appName = packageName;
    PackageManager packageManager = context.getPackageManager();
    try {
        appName = packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)).toString();
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return appName;
}

(Update)

Unfortunately, google forbid applications from receiving the list of currently running apps. (It was since 5.1.1, but i still can get app list in test devices runned android 7.1)

Now you can get only list of recently used apps (with request runtime permission for it) by using UsageStatsManager, for example like here Android 5.1.1 and above - getRunningAppProcesses() returns my application package only

So if user close or exit from fake location app, i can't determinate it.

And now, to get list of fake location apps, i try to get locations, and if location.isFromMockProvider() return true, i scan device for all installed apps, that need permission to access mock location like this:

public static List<String> getListOfFakeLocationAppsFromAll(Context context) {
    List<String> fakeApps = new ArrayList<>();
    List<ApplicationInfo> packages = context.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA);
    for (ApplicationInfo aPackage : packages) {
        boolean isSystemPackage = ((aPackage.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
        if(!isSystemPackage && hasAppPermission(context, aPackage.packageName, "android.permission.ACCESS_MOCK_LOCATION")){
            fakeApps.add(getApplicationName(context, aPackage.packageName));
        }
    }
    return fakeApps;
}
John
  • 1,447
  • 15
  • 16
  • In my case 'getListOfFakeLocationApps' always return empty list. – Makalele Sep 05 '19 at 13:03
  • @Makalele Please try to debug the code and see why it's empty. Are you really replacing the location on non-rooted device? Can you help me improve my code? – John Sep 06 '19 at 10:41
  • getRunningApps returns only my app, I don't have rooted device. There are no exceptions thrown. – Makalele Sep 06 '19 at 13:07
5

Answers on This SO question and to a lesser extent the answers on This SO question seem to indicate you are suffering from an unfortunate Caching issue in the FusedLocationApi caused by onLocationChanged being called with an out of date timestamp (thus ignoring the result as it thinks there is already newer data).

To quote Reno's answer:

Unless you have not changed ... so that new APs can be discovered, I'm afraid you will get only cached locations. If you want fresh locations use the GPS provider.

The solution will be to instead call a location from the GPS Provider like so:

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new MyLocationListener();
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, locationListener);

(The code above comes from a longer example here)

Community
  • 1
  • 1
Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
2

I use this method in my projects and it work perfectly till now:

for api < 18

//returns true if mock location enabled, false if not enabled.
public static boolean isMockLocationOn(Context context) {
    if (Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION).equals("0"))
        return false;
    else
        return true;
}

For api >= 18 you should use

location.isFromMockProvider();

The point is location.isFromMockProvider is buggy and some times it will show a mocked location as its OK !!! There is a workaround in this link with full detail Location on Android: Stop Mocking Me!

the approach is :

  • Remember the most recent location labeled as a mock

  • If a new “non-mock” reading is within 1km of the last mock, reject it.

  • Only clear the last mock location after 20 consecutive “non-mock” readings.

Omid Heshmatinia
  • 5,089
  • 2
  • 35
  • 50
  • 2
    in android > 6, this method won't work anymore. it mean your method always return false. I am sure, you can test it – Linh Mar 12 '17 at 05:45
  • thank you but there are some missing here. please check my question again. now I am using `isFromMockProvider` and there is some problem with `isFromMockProvider` so I ask this question. Second, the link you have given is not working well in that case: User fake location by FakeGPS app then user disable fake location before go to my application. At this time, `most recent location labeled as a mock` is not exist and `isFromMockProvider ` = false – Linh Mar 12 '17 at 08:07