Обнаружение или предупреждение, если пользователь использует поддельное местоположение

Я заблокирую пользователям использование моего приложения, если они имитируют местоположение. Поэтому я использую isFromMockProvider, чтобы проверить, не является ли местоположение поддельным (следуйте здесь). Но в некоторых случаях isFromMockProvider() может возвращать false для поддельных локаций.

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

В моем случае: я использую приложение Поддельное местоположение GPS для подделки местоположения, затем отключаю поддельное местоположение и захожу в свое приложение. Затем onLocationChanged возвращает поддельное местоположение с isFromMockProvider() = false

Видеомагнитофон: https://www.youtube.com/watch?v=rWVvjOCaZiI (в этом видео мое текущее местоположение - 16.06, 108.21, поддельное местоположение - 36.26, 138.28. Вы можете видеть в последнем видео, что местоположение - 36.26, 138.28, но isFromMockProvider = false)

Есть ли способ определить, использует ли пользователь в этом случае поддельное местоположение? Буду признателен за любую помощь или предложение.
ДЕМО проект

Ответ 1

Рискуя реалистичный ответ

Я хотел бы дать ответ, который поможет разработчику понять аспект дизайна связей с общественностью, рискуя критиковать. Честно говоря, нельзя писать большие приложения в вакууме компьютерной науки. Удовлетворение потребностей пользователей и их балансирование с безопасностью является одной из основных проблем в программном интерфейсе и поведенческом дизайне сегодня, особенно в мобильном пространстве.

С этой точки зрения, ваш вопрос: "Есть ли способ определить, использует ли пользователь фальшивое местоположение в этом случае?" не может быть самым актуальным вопросом, с которым вы сталкиваетесь. Я не уклончива, задав этот другой вопрос, который может помочь вам больше, и я могу ответить хорошо: "Есть ли способ безопасно получить данные из пользовательского устройства с помощью геокоординатной прошивки, чтобы его нельзя было подделать?"

Ответ на этот вопрос: "Нет".

Контракт клиента HTTP HTTP

Это не часть контракта клиент-сервер Android или его конкурентов, чтобы гарантировать информацию о местоположении устройства пользователя.

Практическая причина

На самом деле существует рыночная сила, которая, вероятно, будет настаивать на такой гарантии на неопределенный срок. Многие владельцы устройств (и ваши пользователи) хотят получить контроль над тем, знают ли люди свое истинное местоположение для обеспечения конфиденциальности, а также из соображений безопасности дома и семьи.

Решение

Следующий вопрос, который вы можете задать себе как разработчик вашего программного обеспечения, это "Как приложение или библиотека могут работать и обеспечивать потребности, которые я хочу заполнить определенным процентом сообщества пользователей, использующим сегодняшнее (или завтрашнее) местоположение spoofing software?"

Если вы пишете программное обеспечение бизнес-аналитики или в вашей системе есть какой-то другой статистический аспект, вам нужен программный эквивалент баров ошибок. Если вы показываете статистику, то бары ошибок будут подходящей функцией графического отображения. Оценка процента дисковых переключателей местоположения из популяции пользователей потребует дальнейшего изучения.

Ответ 2

Я использую два способа определения поддельных мест. Во-первых, я проверяю расположение макета, как в другом коде здесь.

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");
    }
}

Во-вторых, я проверяю работающие приложения и службы, которым необходимо разрешение для доступа к макету.

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;
}

(обновление)

К сожалению, Google запрещает приложениям получать список запущенных приложений. (Это было с 5.1.1, но я все еще могу получить список приложений на тестовых устройствах под управлением Android 7.1)

Теперь вы можете получить только список недавно использованных приложений (с запросом времени выполнения для него), используя UsageStatsManager, например, как здесь Android 5.1.1 и выше - getRunningAppProcesses() возвращает только мой пакет приложения

Поэтому, если пользователь закроется или выйдет из приложения с поддельным местоположением, я не смогу его определить.

И теперь, чтобы получить список поддельных приложений определения местоположения, я пытаюсь получить местоположения, и если location.isFromMockProvider() возвращает true, я сканирую устройство на наличие всех установленных приложений, которым необходимо разрешение для доступа к фиктивному местоположению, например так:

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;
}

Ответ 3

Ответы на Этот вопрос SO и, в меньшей степени, ответы на Этот вопрос SO, кажется, указывает вы страдаете от неудачной проблемы кэширования в FusedLocationApi, вызванной вызовом onLocationChanged с устаревшей меткой времени (таким образом, игнорируя результат, поскольку он считает, что есть уже новые данные).

Чтобы процитировать ответ Reno:

Если вы не изменились... чтобы новые точки доступа были обнаружены, я боюсь, вы получите только кэшированные местоположения. Если вы хотите, чтобы новые места использовали провайдер GPS.

Решение состоит в том, чтобы вместо этого вызвать местоположение из поставщика GPS:

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

(Код выше приведен из более длинного примера здесь)

Ответ 4

Я использую этот метод в своих проектах, и он отлично работает до сих пор:

для 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;
}

Для api >= 18 вы должны использовать

location.isFromMockProvider();

Точка location.isFromMockProvider является ошибкой, и несколько раз она отображает насмешливое местоположение как ОК! Существует обходное решение в этой ссылке с полной детализацией Местоположение на Android: перестаньте насмехаться!

подход:

  • Помните последнее место, помеченное как макет

  • Если новое "не-фальшивое" чтение находится в пределах 1 км от последнего макета, отклоните он.

  • Очистите только последнее место из mock после 20 последовательных "несовременных" показания.