Создание пробного приложения Android, срок действия которого истекает через фиксированный период времени

103

У меня есть приложение, которое я хочу вывести на рынок как платное. Я хотел бы иметь другую версию, которая была бы «пробной» с ограничением по времени, скажем, 5 дней?

Как я могу это сделать?

Том
источник
Google действительно должен поддерживать это в Play Services!
Powder366
@ Powder366 на самом деле Google поддерживает это, см. developer.android.com/google/play/billing/…
Yazazzello

Ответы:

186

В настоящее время большинство разработчиков добиваются этого, используя один из следующих 3 методов.

Первый подход легко обойти: при первом запуске приложения сохраните дату / время в файле, базе данных или общих настройках и каждый раз, когда вы запускаете приложение после этого, проверяйте, закончился ли пробный период. Это легко обойти, поскольку удаление и повторная установка предоставят пользователю еще один пробный период.

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

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

Всегда рекомендуется выполнять эти проверки в onCreate. Если истечение срока истекло, появится всплывающее окно AlertDialog со ссылкой на полную версию приложения. Включите только кнопку «ОК», и как только пользователь нажмет «ОК», вызовите «finish ()», чтобы завершить действие.

snctln
источник
2
Фантастический ответ. Как вы говорите, я считаю, что второй вариант, возможно, лучший. Жаль, что сами Google не предлагают какой-то системы лицензирования, поскольку это может побудить как мелких, так и крупных разработчиков брендов создавать еще больше приложений для Android.
Tom
8
Также я бы не стал проверять при запуске. Ваша цель - продать приложение, а не наказать пользователя (это просто бонус;). Если вы настроили проверять каждые 2 минуты во время работы, вы позволяете пользователю начать что-то делать, а затем понимаете, что он должен заплатить. Если вы упростите оплату и вернетесь к работе (я не уверен, сможете ли вы в Android), я думаю, вы продадите больше, чем проверяете во время onCreate.
Whaledawg
4
@Whaledawg: вам действительно нужно запустить собственный сервер, потому что сервер хранит идентификатор телефона и время первого запуска в базе данных, которая затем сравнивается с более поздними. Также, когда вы выполняете проверку, это чисто предпочтение разработчика, я использовал жесткий закодированная бомба замедленного действия в игре с отличными результатами. Будет загружено все приложение, но пользователь может взаимодействовать только с видимым диалоговым окном, в этом диалоговом окне есть кнопка, которая переводит пользователя прямо на страницу покупки игры. Пользователи, похоже, не возражают против AFAIK, поскольку эта игра была в топ-10 платных приложений с момента открытия Android Market.
snctln
11
Всем, кто не хочет использовать вариант 3 из-за дополнительной настройки сервера, обратите внимание на Parse.com - это синхронизация.
Joel Skrepnek
3
Что подразумевается под жесткой датой окончания пробного периода? Означает ли это, что вы всегда будете выпускать новые версии пробного приложения с разными жестко заданными датами в будущем?
Джаспер
21

Я разработал Android Trial SDK, который вы можете просто добавить в свой проект Android Studio, и он позаботится обо всем управлении на стороне сервера (включая льготные периоды в автономном режиме).

Чтобы использовать это, просто

Добавьте библиотеку в свой основной модуль build.gradle

dependencies {
  compile 'io.trialy.library:trialy:1.0.2'
}

Инициализируйте библиотеку в onCreate()методе вашего основного действия

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Initialize the library and check the current trial status on every launch
    Trialy mTrialy = new Trialy(mContext, "YOUR_TRIALY_APP_KEY");
    mTrialy.checkTrial(TRIALY_SKU, mTrialyCallback);
}

Добавьте обработчик обратного вызова:

private TrialyCallback mTrialyCallback = new TrialyCallback() {
    @Override
    public void onResult(int status, long timeRemaining, String sku) {
        switch (status){
            case STATUS_TRIAL_JUST_STARTED:
                //The trial has just started - enable the premium features for the user
                 break;
            case STATUS_TRIAL_RUNNING:
                //The trial is currently running - enable the premium features for the user
                break;
            case STATUS_TRIAL_JUST_ENDED:
                //The trial has just ended - block access to the premium features
                break;
            case STATUS_TRIAL_NOT_YET_STARTED:
                //The user hasn't requested a trial yet - no need to do anything
                break;
            case STATUS_TRIAL_OVER:
                //The trial is over
                break;
        }
        Log.i("TRIALY", "Trialy response: " + Trialy.getStatusMessage(status));
    }

};

Чтобы начать пробную версию, позвоните. mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback); Ваш ключ приложения и SKU пробной версии можно найти на панели инструментов разработчика Trialy .

Ник
источник
Требуются данные для включения?
Sivaram Boina
Trialy не является надежным
Amir Dora
1
@AmirDe Привет, Амир, не могли бы вы сообщить мне, что у вас не работает? Я рад помочь, support@trialy.io Trialy отлично работает для 1000+ пользователей
Ник
@Nick не знает, почему на моем устройстве работает леденец на палочке Android, когда я устанавливаю день на панели управления, затем через несколько минут день отображается отрицательным значением, в нем говорится, что пробная версия истекла, хотя у меня еще много дней на панели инструментов. Я также тестировал на устройстве nougat, похоже, он отлично работает на naugat. возможно, у него есть проблемы с совместимостью более старых версий Android
Амир Дора
1
Пользуюсь этим сервисом с 2016 года, постоянно работает нормально. Я использовал это и в своих официальных проектах. Это должен быть принятый ответ.
Тарик Махмуд
17

Это старый вопрос, но в любом случае, может быть, это кому-то поможет.

Если вы хотите использовать самый упрощенный подход (который не удастся, если приложение будет удалено / переустановлено или пользователь вручную изменит дату устройства), это может быть так:

private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;

@Override
protected void onCreate(Bundle state){
    SharedPreferences preferences = getPreferences(MODE_PRIVATE);
    String installDate = preferences.getString("InstallDate", null);
    if(installDate == null) {
        // First run, so save the current date
        SharedPreferences.Editor editor = preferences.edit();
        Date now = new Date();
        String dateString = formatter.format(now);
        editor.putString("InstallDate", dateString);
        // Commit the edits!
        editor.commit();
    }
    else {
        // This is not the 1st run, check install date
        Date before = (Date)formatter.parse(installDate);
        Date now = new Date();
        long diff = now.getTime() - before.getTime();
        long days = diff / ONE_DAY;
        if(days > 30) { // More than 30 days?
             // Expired !!!
        }
    }

    ...
}
Канер
источник
На самом деле, если вы используете SharedPreferences и Android 2.2 Froyo или выше, пока вы реализуете API резервного копирования данных для синхронизации данных Google, пользователь не сможет избежать этого на разных устройствах или путем удаления, только перейдя в Настройки> Приложения и очистка данных. Кроме того , метод на сегодняшний день getTimeне getTimeInMillis.
Том
Не согласен, это также не сработает, если пользователь изменит дату устройства вручную, а также что, если пользователь очистит данные вручную?
Мохаммед Ажаруддин Шейх
@Caner это хорошо проверить с помощью sharedprefrence, но пользователь очистит память от установки-> диспетчера приложений и повторно использует, тогда что будет делать?
CoronaPintu
@CoronaPintu, так что этот подход также будет опробован с firebase, это будет идеальная комбинация, и пробный период закончится, даже если приложение будет удалено.
Нур Хоссейн
объедините и добавьте подход с ответом "странные времена", это сделает подход идеальным.
Нур Хоссейн,
10

Этот вопрос и ответ snctln вдохновили меня на работу над решением, основанным на методе 3, в качестве моей бакалаврской диссертации. Я знаю, что текущий статус не предназначен для продуктивного использования, но мне хотелось бы услышать, что вы об этом думаете! Вы бы использовали такую ​​систему? Хотели бы вы видеть его как облачный сервис (без проблем с настройкой сервера)? Беспокоитесь о проблемах безопасности или стабильности?

Как только я закончу бакалавриат, я хочу продолжить работу над программой. Итак, пришло время узнать ваше мнение!

Исходный код размещен на GitHub https://github.com/MaChristmann/mobile-trial

Некоторая информация о системе: - Система состоит из трех частей: библиотеки Android, сервера node.js и конфигуратора для управления несколькими пробными приложениями и учетными записями издателей / разработчиков.

  • Он поддерживает только пробные версии, основанные на времени, и использует вашу учетную запись (игровой магазин или другую), а не идентификатор телефона.

  • Для библиотеки Android он основан на библиотеке проверки лицензирования Google Play. Я модифицировал его для подключения к серверу node.js, и, кроме того, библиотека пытается распознать, изменил ли пользователь системную дату. Он также кэширует полученную пробную лицензию в зашифрованных общих настройках AES. Вы можете настроить время действия кеша с помощью конфигуратора. Если пользователь «очищает данные», библиотека принудительно выполняет проверку на стороне сервера.

  • Сервер использует https, а также использует цифровую подпись для ответа о проверке лицензии. Он также имеет API для пробных приложений CRUD и пользователей (издателей и разработчиков). Разработчики библиотеки лицензирования Verfication Library могут протестировать реализацию своего поведения в пробном приложении с результатами теста. Таким образом, в конфигураторе вы можете явно указать ответ лицензии на «лицензировано», «не лицензировано» или «ошибка сервера».

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

  • Все под лицензией Apache 2.0

Мартин Кристманн
источник
2
Вы только что спасли мне день, спасибо за тяжелую работу. Я просто подумал написать такое же решение с использованием зашифрованной конфигурации, полученной только один раз, и сохранения открытого ключа внутри приложения. Итак, основная проблема, которую я вижу только в том, как предоставить лицензию? Вам все равно может понадобиться уникальный идентификатор, который можно носить с телефоном, чтобы избежать нескольких грантов.
user2305886
6

Самый простой и лучший способ сделать это - реализовать BackupSharedPreferences.

Настройки сохранятся, даже если приложение будет удалено и переустановлено.

Просто сохраните дату установки в качестве предпочтения, и все готово.

Вот теория: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

Вот пример: резервное копирование Android SharedPreferences не работает

псторли
источник
3
Пользователь может отключить резервное копирование в настройках системы.
Патрик
5

Подход 4: используйте время установки приложения.

Так как уровень API 9 (Android 2.3.2, 2.3.1, Android 2.3, Gingerbread) есть firstInstallTime и lastUpdateTime в PackageInfo.

Чтобы узнать больше: Как получить время установки приложения с Android

18446744073709551615
источник
Можно ли использовать метод 1 из ответа snctln с этим надежно, без того, чтобы его легко обойти, или у него такая же или похожая проблема?
jwinn
Я опробовал этот метод. Хорошая сторона в том, что он работает, даже если данные очищены. Плохая сторона в том, что это можно обойти путем удаления / переустановки.
Жан-Филипп Жоден
может подтвердить: на моем Pixel при удалении и повторной установке приложения происходит сброс времени первой установки. что делает сброс пробной
версии
3

Теперь в последней версии была добавлена ​​бесплатная пробная подписка для Android, вы можете разблокировать все функции своего приложения только после покупки подписки в приложении на бесплатный пробный период. Это позволит пользователю использовать ваше приложение в течение пробного периода. Если приложение все еще будет удалено после пробного периода, деньги за подписку будут переведены вам. Не пробовал, просто поделюсь идеей.

Вот документация

Вин
источник
2
Я бы хотел, чтобы это сработало для разовых покупок. Работает только для подписок, которые могут быть годовыми или ежемесячными.
jwinn
3

На мой взгляд, лучший способ сделать это - просто использовать базу данных Firebase Realtime:

1) Добавьте поддержку Firebase в свое приложение

2) Выберите «Анонимная аутентификация», чтобы пользователю не приходилось регистрироваться или даже знать, что вы делаете. Это гарантированно будет связано с учетной записью пользователя, прошедшей аутентификацию в настоящее время, и поэтому будет работать на всех устройствах.

3) Используйте API базы данных в реальном времени, чтобы установить значение для "installed_date". Во время запуска просто получите это значение и используйте его.

Я сделал то же самое, и он отлично работает. Я смог проверить это при удалении / повторной установке, и значение в базе данных в реальном времени осталось прежним. Таким образом, ваш пробный период работает на нескольких пользовательских устройствах. Вы даже можете изменить версию install_date, чтобы приложение сбрасывало дату пробной версии для каждого нового основного выпуска.

ОБНОВЛЕНИЕ : после небольшого тестирования кажется, что анонимный Firebase, похоже, выделяет другой идентификатор на случай, если у вас разные устройства, и не гарантируется между повторными установками: / Единственный гарантированный способ - использовать Firebase, но привязать его к своему Google учетная запись. Это должно сработать, но потребует дополнительного шага, на котором пользователю сначала нужно войти в систему / зарегистрироваться.

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

странные времена
источник
У меня такие же требования к моему приложению для Android, и у меня есть собственная база данных / веб-сервер. Пользователям не требуется вход в систему, поэтому я планировал сохранить идентификатор устройства с установленной_датой, это сработает?
user636525
@strangetimes, я думаю, ваше решение работает лучше всего, вы можете предоставить дополнительную информацию? спасибо
DayDayHappy
Этот поток stackoverflow.com/q/41733137/1396068 предполагает, что после переустановки приложения вы получите новый идентификатор пользователя, то есть новый пробный период
Фабиан Штрайтель
3

После просмотра всех вариантов в этой и других темах, вот мои выводы

Общие настройки, база данных Может быть очищена в настройках Android, потеряна после переустановки приложения. Можно создать резервную копию с помощью механизма резервного копирования Android и восстановить после переустановки. Резервное копирование не всегда может быть доступно, хотя должно быть на большинстве устройств.

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

PackageInfo.firstInstallTime после переустановки, но стабильно во всех обновлениях

Войдите в какой-нибудь аккаунт Неважно, это их учетная запись Google через Firebase или учетная запись на вашем собственном сервере: пробная версия привязана к учетной записи. Создание новой учетной записи приведет к сбросу пробной версии.

Анонимный вход в Firebase. Вы можете войти в систему анонимно и хранить данные для них в Firebase. Но, по- видимому, переустановка приложения и, возможно, другие недокументированные события могут дать пользователю новый анонимный идентификатор , сбросив пробное время. (Сами Google не предоставляют много документации по этому поводу)

ANDROID_ID Может быть недоступен и может измениться при определенных обстоятельствах , например, при сбросе к заводским настройкам. Мнения о том, стоит ли использовать это для идентификации устройств, разошлись.

Рекламный идентификатор Google Play может быть сброшен пользователем. Может быть отключено пользователем, отказавшись от отслеживания рекламы.

Сброс InstanceID при переустановке . Сброс в случае события безопасности. Может быть сброшен вашим приложением.

Какие (комбинации) методов работают для вас, зависит от вашего приложения и от того, сколько усилий, по вашему мнению, средний Джон приложит для получения еще одного пробного периода. Я бы рекомендовал избегать использования только анонимных Firebase и Advertising ID из-за их нестабильности. Похоже, что многофакторный подход даст наилучшие результаты. Какие факторы доступны вам, зависит от вашего приложения и его разрешений.

Для моего собственного приложения я обнаружил, что общие настройки + firstInstallTime + резервное копирование настроек являются наименее навязчивым, но также достаточно эффективным методом. Вы должны убедиться, что запрашиваете резервную копию только после проверки и сохранения времени запуска пробной версии в общих настройках. Значения в общих Prefs должны иметь приоритет над firstInstallTime. Затем пользователь должен переустановить приложение, запустить его один раз, а затем очистить данные приложения, чтобы сбросить пробную версию, что является довольно большой работой. Однако на устройствах без резервного транспорта пользователь может сбросить пробную версию, просто переустановив ее.

Я сделал этот подход доступным в виде расширяемой библиотеки .

Фабиан Штрайтель
источник
1

По определению, все платные приложения для Android, представленные на рынке, можно оценить в течение 24 часов после покупки.

Там есть кнопка «Удалить и вернуть», которая через 24 часа меняется на «Удалить».

Я бы сказал, что эта кнопка слишком заметна!

AlexJReid
источник
17
Обратите внимание, что период возврата теперь составляет всего 15 минут.
Хитрости
1

Я столкнулся с этим вопросом при поиске той же проблемы, я думаю, мы можем использовать бесплатный api даты, например http://www.timeapi.org/utc/now или какой-либо другой api даты, чтобы проверить истечение срока действия приложения trail. этот способ эффективен, если вы хотите доставить демоверсию и беспокоиться об оплате и требовать исправления демоверсии. :)

найдите код ниже

public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
    processCurrentTime();
    super.onResume();
}

private void processCurrentTime() {
    if (!isDataConnectionAvailable(ValidationActivity.this)) {
        showerrorDialog("No Network coverage!");
    } else {
        String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
        new CallAPI().execute(urlString);
    }
}

private void showerrorDialog(String data) {
    Dialog d = new Dialog(ValidationActivity.this);
    d.setTitle("LS14");
    TextView tv = new TextView(ValidationActivity.this);
    tv.setText(data);
    tv.setPadding(20, 30, 20, 50);
    d.setContentView(tv);
    d.setOnDismissListener(new OnDismissListener() {
        @Override
        public void onDismiss(DialogInterface dialog) {
            finish();
        }
    });
    d.show();
}

private void checkExpiry(int isError, long timestampinMillies) {
    long base_date = 1392878740000l;// feb_19 13:8 in GMT;
    // long expiryInMillies=1000*60*60*24*5;
    long expiryInMillies = 1000 * 60 * 10;
    if (isError == 1) {
        showerrorDialog("Server error, please try again after few seconds");
    } else {
        System.out.println("fetched time " + timestampinMillies);
        System.out.println("system time -" + (base_date + expiryInMillies));
        if (timestampinMillies > (base_date + expiryInMillies)) {
            showerrorDialog("Demo version expired please contact vendor support");
            System.out.println("expired");
        }
    }
}

private class CallAPI extends AsyncTask<String, String, String> {
    @Override
    protected void onPreExecute() {
        // TODO Auto-generated method stub
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String... params) {
        String urlString = params[0]; // URL to call
        String resultToDisplay = "";
        InputStream in = null;
        // HTTP Get
        try {
            URL url = new URL(urlString);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            resultToDisplay = convertStreamToString(in);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return e.getMessage();
        }
        return resultToDisplay;
    }

    protected void onPostExecute(String result) {
        int isError = 1;
        long timestamp = 0;
        if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
            System.out.println("Error $$$$$$$$$");
        } else {
            String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
            System.out.println(strTime);
            try {
                timestamp = Long.parseLong(strTime) * 1000;
                isError = 0;
            } catch (NumberFormatException ne) {
            }
        }
        checkExpiry(isError, timestamp);
    }

} // end CallAPI

public static boolean isDataConnectionAvailable(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = connectivityManager.getActiveNetworkInfo();
    if (info == null)
        return false;

    return connectivityManager.getActiveNetworkInfo().isConnected();
}

public String convertStreamToString(InputStream is) throws IOException {
    if (is != null) {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            is.close();
        }
        return writer.toString();
    } else {
        return "";
    }
}

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub

}
}

его рабочее решение .....

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

Вот как я поступил: я создал 2 приложения, одно с пробной активностью, другое без,

я загрузил приложение без пробной активности в Play Store как платное приложение,

и тот, у которого пробная активность как бесплатное приложение.

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

NB: я использовал вариант 3 вроде @snctln, но с изменениями

во-первых , я не зависел от времени устройства, я получил свое время из файла php, который выполняет пробную регистрацию в db,

во-вторых , я использовал серийный номер устройства, чтобы однозначно идентифицировать каждое устройство,

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

Итак, вот мой код (для пробной деятельности):

package com.example.mypackage.my_app.Start_Activity.activity;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.onlinewisdom.cbn_app.R;
import com.example.mypackage.my_app.Start_Activity.app.Config;
import com.example.mypackage.my_app.Start_Activity.data.TrialData;
import com.example.mypackage.my_app.Start_Activity.helper.connection.Connection;
import com.google.gson.Gson;

import org.json.JSONObject;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import cn.pedant.SweetAlert.SweetAlertDialog;

public class Trial extends AppCompatActivity {
    Connection check;
    SweetAlertDialog pDialog;
    TextView tvPleaseWait;
    private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;

    String BASE_URL = Config.BASE_URL;
    String BASE_URL2 = BASE_URL+ "/register_trial/"; //http://ur link to ur API

    //KEY
    public static final String KEY_IMEI = "IMEINumber";

    private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
    private final long ONE_DAY = 24 * 60 * 60 * 1000;

    SharedPreferences preferences;
    String installDate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_trial);

        preferences = getPreferences(MODE_PRIVATE);
        installDate = preferences.getString("InstallDate", null);

        pDialog = new SweetAlertDialog(this, SweetAlertDialog.PROGRESS_TYPE);
        pDialog.getProgressHelper().setBarColor(Color.parseColor("#008753"));
        pDialog.setTitleText("Loading...");
        pDialog.setCancelable(false);

        tvPleaseWait = (TextView) findViewById(R.id.tvPleaseWait);
        tvPleaseWait.setText("");

        if(installDate == null) {
            //register app for trial
            animateLoader(true);
            CheckConnection();
        } else {
            //go to main activity and verify there if trial period is over
            Intent i = new Intent(Trial.this, MainActivity.class);
            startActivity(i);
            // close this activity
            finish();
        }

    }

    public void CheckConnection() {
        check = new Connection(this);
        if (check.isConnected()) {
            //trigger 'loadIMEI'
            loadIMEI();
        } else {
            errorAlert("Check Connection", "Network is not detected");
            tvPleaseWait.setText("Network is not detected");
            animateLoader(false);
        }
    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //Changes 'back' button action
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            finish();
        }
        return true;
    }

    public void animateLoader(boolean visibility) {
        if (visibility)
            pDialog.show();
        else
            pDialog.hide();
    }

    public void errorAlert(String title, String msg) {
        new SweetAlertDialog(this, SweetAlertDialog.ERROR_TYPE)
                .setTitleText(title)
                .setContentText(msg)
                .show();
    }

    /**
     * Called when the 'loadIMEI' function is triggered.
     */
    public void loadIMEI() {
        // Check if the READ_PHONE_STATE permission is already available.
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            // READ_PHONE_STATE permission has not been granted.
            requestReadPhoneStatePermission();
        } else {
            // READ_PHONE_STATE permission is already been granted.
            doPermissionGrantedStuffs();
        }
    }


    /**
     * Requests the READ_PHONE_STATE permission.
     * If the permission has been denied previously, a dialog will prompt the user to grant the
     * permission, otherwise it is requested directly.
     */
    private void requestReadPhoneStatePermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.READ_PHONE_STATE)) {
            // Provide an additional rationale to the user if the permission was not granted
            // and the user would benefit from additional context for the use of the permission.
            // For example if the user has previously denied the permission.
            new AlertDialog.Builder(Trial.this)
                    .setTitle("Permission Request")
                    .setMessage(getString(R.string.permission_read_phone_state_rationale))
                    .setCancelable(false)
                    .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            //re-request
                            ActivityCompat.requestPermissions(Trial.this,
                                    new String[]{Manifest.permission.READ_PHONE_STATE},
                                    MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
                        }
                    })
                    .setIcon(R.drawable.warning_sigh)
                    .show();
        } else {
            // READ_PHONE_STATE permission has not been granted yet. Request it directly.
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE},
                    MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
        }
    }

    /**
     * Callback received when a permissions request has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
            // Received permission result for READ_PHONE_STATE permission.est.");
            // Check if the only required permission has been granted
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // READ_PHONE_STATE permission has been granted, proceed with displaying IMEI Number
                //alertAlert(getString(R.string.permision_available_read_phone_state));
                doPermissionGrantedStuffs();
            } else {
                alertAlert(getString(R.string.permissions_not_granted_read_phone_state));
            }
        }
    }

    private void alertAlert(String msg) {
        new AlertDialog.Builder(Trial.this)
                .setTitle("Permission Request")
                .setMessage(msg)
                .setCancelable(false)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // do somthing here
                    }
                })
                .setIcon(R.drawable.warning_sigh)
                .show();
    }

    private void successAlert(String msg) {
        new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE)
                .setTitleText("Success")
                .setContentText(msg)
                .setConfirmText("Ok")
                .setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
                    @Override
                    public void onClick(SweetAlertDialog sDialog) {
                        sDialog.dismissWithAnimation();
                        // Prepare intent which is to be triggered
                        //Intent i = new Intent(Trial.this, MainActivity.class);
                        //startActivity(i);
                    }
                })
                .show();
    }

    public void doPermissionGrantedStuffs() {
        //Have an  object of TelephonyManager
        TelephonyManager tm =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        //Get IMEI Number of Phone  //////////////// for this example i only need the IMEI
        String IMEINumber = tm.getDeviceId();

        /************************************************
         * **********************************************
         * This is just an icing on the cake
         * the following are other children of TELEPHONY_SERVICE
         *
         //Get Subscriber ID
         String subscriberID=tm.getDeviceId();

         //Get SIM Serial Number
         String SIMSerialNumber=tm.getSimSerialNumber();

         //Get Network Country ISO Code
         String networkCountryISO=tm.getNetworkCountryIso();

         //Get SIM Country ISO Code
         String SIMCountryISO=tm.getSimCountryIso();

         //Get the device software version
         String softwareVersion=tm.getDeviceSoftwareVersion()

         //Get the Voice mail number
         String voiceMailNumber=tm.getVoiceMailNumber();


         //Get the Phone Type CDMA/GSM/NONE
         int phoneType=tm.getPhoneType();

         switch (phoneType)
         {
         case (TelephonyManager.PHONE_TYPE_CDMA):
         // your code
         break;
         case (TelephonyManager.PHONE_TYPE_GSM)
         // your code
         break;
         case (TelephonyManager.PHONE_TYPE_NONE):
         // your code
         break;
         }

         //Find whether the Phone is in Roaming, returns true if in roaming
         boolean isRoaming=tm.isNetworkRoaming();
         if(isRoaming)
         phoneDetails+="\nIs In Roaming : "+"YES";
         else
         phoneDetails+="\nIs In Roaming : "+"NO";


         //Get the SIM state
         int SIMState=tm.getSimState();
         switch(SIMState)
         {
         case TelephonyManager.SIM_STATE_ABSENT :
         // your code
         break;
         case TelephonyManager.SIM_STATE_NETWORK_LOCKED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_PIN_REQUIRED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_PUK_REQUIRED :
         // your code
         break;
         case TelephonyManager.SIM_STATE_READY :
         // your code
         break;
         case TelephonyManager.SIM_STATE_UNKNOWN :
         // your code
         break;

         }
         */
        // Now read the desired content to a textview.
        //tvPleaseWait.setText(IMEINumber);
        UserTrialRegistrationTask(IMEINumber);
    }

    /**
     * Represents an asynchronous login task used to authenticate
     * the user.
     */
    private void UserTrialRegistrationTask(final String IMEINumber) {
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, BASE_URL2+IMEINumber, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Gson gson = new Gson();
                        TrialData result = gson.fromJson(String.valueOf(response), TrialData.class);
                        animateLoader(false);
                        if ("true".equals(result.getError())) {
                            errorAlert("Error", result.getResult());
                            tvPleaseWait.setText("Unknown Error");
                        } else if ("false".equals(result.getError())) {
                            //already created install/trial_start date using the server
                            // so just getting the date called back
                            Date before = null;
                            try {
                                before = (Date)formatter.parse(result.getResult());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            Date now = new Date();
                            assert before != null;
                            long diff = now.getTime() - before.getTime();
                            long days = diff / ONE_DAY;
                            // save the date received
                            SharedPreferences.Editor editor = preferences.edit();
                            editor.putString("InstallDate", String.valueOf(days));
                            // Commit the edits!
                            editor.apply();
                            //go to main activity and verify there if trial period is over
                            Intent i = new Intent(Trial.this, MainActivity.class);
                            startActivity(i);
                            // close this activity
                            finish();
                            //successAlert(String.valueOf(days));
                            //if(days > 5) { // More than 5 days?
                                // Expired !!!
                            //}
                            }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        animateLoader(false);
                        //errorAlert(error.toString());
                        errorAlert("Check Connection", "Could not establish a network connection.");
                        tvPleaseWait.setText("Network is not detected");
                    }
                })

        {
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put(KEY_IMEI, IMEINumber);
                return params;
            }
        };

        RequestQueue requestQueue = Volley.newRequestQueue(this);
        requestQueue.add(jsonObjectRequest);
    }


}

Мой php файл выглядит так (это технология REST-slim):

/**
     * registerTrial
     */
    public function registerTrial($IMEINumber) {
        //check if $IMEINumber already exist
        // Instantiate DBH
        $DBH = new PDO_Wrapper();
        $DBH->query("SELECT date_reg FROM trials WHERE device_id = :IMEINumber");
        $DBH->bind(':IMEINumber', $IMEINumber);
        // DETERMINE HOW MANY ROWS OF RESULTS WE GOT
        $totalRows_registered = $DBH->rowCount();
        // DETERMINE HOW MANY ROWS OF RESULTS WE GOT
        $results = $DBH->resultset();

        if (!$IMEINumber) {
            return 'Device serial number could not be determined.';
        } else if ($totalRows_registered > 0) {
            $results = $results[0];
            $results = $results['date_reg'];
            return $results;
        } else {
            // Instantiate variables
            $trial_unique_id = es_generate_guid(60);
            $time_reg = date('H:i:s');
            $date_reg = date('Y-m-d');

            $DBH->beginTransaction();
            // opening db connection
            //NOW Insert INTO DB
            $DBH->query("INSERT INTO trials (time_reg, date_reg, date_time, device_id, trial_unique_id) VALUES (:time_reg, :date_reg, NOW(), :device_id, :trial_unique_id)");
            $arrayValue = array(':time_reg' => $time_reg, ':date_reg' => $date_reg, ':device_id' => $IMEINumber, ':trial_unique_id' => $trial_unique_id);
            $DBH->bindArray($arrayValue);
            $subscribe = $DBH->execute();
            $DBH->endTransaction();
            return $date_reg;
        }

    }

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

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

Надеюсь, это спасет душу ... когда-нибудь

Удачного кодирования ...

Мертвый парень
источник
0

@snctlnВариант 3 можно легко выполнить, добавив файл php на веб-сервер с установленными php и mysql, как и многие из них.

Со стороны Android идентификатор (идентификатор устройства, учетная запись google или что угодно) передается в качестве аргумента в URL-адресе с помощью HttpURLConnection, а php возвращает дату первой установки, если она существует в таблице, или вставляет новую строку и он возвращает текущую дату.

Он отлично работает для меня.

Если будет время, выложу код!

Удачи !

Луис Фелисарт
источник
подождите, а вы потеряете свой уникальный идентификатор после переустановки приложения? !!
Максим Князев
Этот идентификатор идентифицирует оборудование, сам телефон, пользователь его не видит и не может его изменить. Если он переустановит приложение, веб-служба php обнаружит, что это тот же телефон. С другой стороны, если пользователь сменит телефон, его ждет новый период.
Lluis Felisart