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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

    ...
}

Ответ 3

Я разработал 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.

Ответ 4

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

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

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

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

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

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

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

  • Все находится под лицензией Apache 2.0

Ответ 5

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

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

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

Здесь теория: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

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

Ответ 7

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

Здесь документация

Ответ 8

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

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

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

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

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

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

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

Ответ 9

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

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

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

Ответ 10

Я сталкиваюсь с этим вопросом во время поиска той же проблемы, я думаю, что мы можем использовать бесплатную дату 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

}
}

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

Ответ 11

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

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

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

PackageInfo.firstInstallTime Является reset после переустановки, но стабильной через обновления

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

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

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

Воспроизвести рекламный идентификатор Может быть reset пользователем. Может быть отключен пользователем путем отказа от отслеживания объявлений.

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

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

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

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

Ответ 12

Вот как я пошел по морю, Я создал 2 приложения с пробной деятельностью, без них,

я загрузил файл без пробной активности, чтобы играть в качестве платного приложения,

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

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

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 непосредственно на сервере для людей скачать бесплатно. Но я уверен, что скоро отредактирую этот ответ с помощью решения или ссылки на решение.

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

Счастливое кодирование...

Ответ 13

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

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

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

Если у меня есть время, я отправлю код!

Удачи!