Crear una aplicación de prueba de Android que caduque después de un período de tiempo fijo

StackOverflow https://stackoverflow.com/questions/995719

Pregunta

Tengo una aplicación que quiero lanzar al mercado como aplicación paga.Me gustaría tener otra versión que sería una versión de "prueba" con un límite de tiempo de, digamos, 5 días.

¿Cómo puedo hacer esto?

¿Fue útil?

Solución

En la actualidad la mayoría de los desarrolladores de lograr esto usando uno de los siguientes 3 técnicas.

El primer enfoque es fácil de eludir, la primera vez que se ejecuta la aplicación guardar la fecha / hora en un archivo, base de datos o preferencias compartidas y cada vez que se ejecuta la aplicación después de que la comprobación para ver si ha terminado el período de prueba. Esto es fácil de eludir, porque la desinstalación y reinstalación permitirá al usuario tener otro período de prueba.

El segundo enfoque es más dificil de superar, pero aún circumventable. Utilice una bomba de tiempo codificado. Básicamente, con este enfoque va a ser duro código una fecha final para el juicio, y todos los usuarios que descargar y utilizar la aplicación va a dejar de ser capaz de utilizar la aplicación al mismo tiempo. He utilizado este enfoque, ya que es fácil de implementar y en su mayor parte yo no tenía ganas de pasar por la molestia de la tercera técnica. Los usuarios pueden eludir esta cambiando manualmente la fecha en su teléfono, pero la mayoría de los usuarios no pasar por la molestia de hacer tal cosa.

La tercera técnica es la única manera que he oído a punto de ser realmente capaz de lograr lo que quiere hacer. Usted tendrá que configurar un servidor, y luego cada vez que se inicia la aplicación de su aplicación envía la teléfonos identificador único al servidor. Si el servidor no tiene una entrada para ese teléfono Identificación entonces tiene uno nuevo y toma nota del tiempo. Si el servidor tiene una entrada para la Identificación del teléfono, entonces se hace una simple comprobación para ver si ha expirado el período de prueba. Posteriormente, comunica los resultados de la caducidad de la prueba de verificación de nuevo a su aplicación. Este enfoque no debe ser circumventable, pero requiere la creación de un servidor web y tal.

Siempre es una buena práctica para hacer estas comprobaciones en el onCreate. Si el vencimiento haya terminado emergente un AlertDialog con un a la plena versión de la aplicación. Sólo incluir un botón "OK", y una vez que el usuario hace clic en "Aceptar" hacer una llamada a "finish ()" para poner fin a la actividad.

Otros consejos

he desarrollado una Android SDK Trial que puede caer simplemente en su proyecto Android Studio y que va a tomar cuidar de toda la gestión de servidor para usted (incluyendo períodos de gracia fuera de línea).

Para usarlo, simplemente

Añadir a la biblioteca build.gradle de su módulo principal

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

inicializar la biblioteca en el método onCreate() de su actividad

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

Añadir un manejador de devolución de llamada:

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

};

Para iniciar una prueba, llamada mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback); Su clave de aplicación y el juicio SKU se pueden encontrar en su Trialy Panel del .

Esta es una vieja pregunta, pero de todos modos, tal vez ayude a alguien.

En caso de que quieras ir con el enfoque más simplista(cual fallará si la aplicación se desinstala/reinstala o el usuario cambia la fecha del dispositivo manualmente), así es como podría ser:

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

    ...
}

Hola chicos, esta pregunta y la respuesta de snctln Me inspiró a trabajar en una solución basada en el método 3 como mi tesis de licenciatura.Sé que el estado actual no es para uso productivo, ¡pero me encantaría saber qué piensas al respecto!¿Usarías un sistema así?¿Le gustaría verlo como un servicio en la nube (sin tener problemas para configurar un servidor)?¿Le preocupan cuestiones de seguridad o motivos de estabilidad?Tan pronto como termine el procedimiento de licenciatura, quiero seguir trabajando en el software.¡Así que ahora es el momento en que necesito tus comentarios!

El código fuente está alojado en GitHub https://github.com/MaChristmann/mobile-trial

Alguna información sobre el sistema:- El sistema tiene tres partes, una biblioteca de Android, un servidor node.js y un configurador para administrar múltiples aplicaciones de prueba y cuentas de editor/desarrollador.

  • Solo admite pruebas basadas en tiempo y utiliza su cuenta (Play Store u otra) en lugar de una identificación telefónica.

  • Para la biblioteca de Android, se basa en la biblioteca de verificación de licencias de Google Play.Lo modifiqué para conectarme al servidor node.js y, además, la biblioteca intenta reconocer si un usuario cambió la fecha del sistema.También almacena en caché una licencia de prueba recuperada en Preferencias compartidas cifradas con AES.Puedes configurar el tiempo válido del caché con el configurador.Si un usuario "borra datos", la biblioteca forzará una verificación del lado del servidor.

  • El servidor utiliza https y también firma digital en la respuesta de verificación de licencia.También tiene una API para usuarios y aplicaciones de prueba CRUD (editor y desarrollador).De manera similar a la biblioteca de verificación de licencias, los desarrolladores pueden probar la implementación de su comportamiento en la aplicación de prueba con el resultado de la prueba.Por lo tanto, en el configurador puede establecer explícitamente la respuesta de su licencia como "con licencia", "sin licencia" o "error del servidor".

  • Si actualiza su aplicación con una nueva característica increíble, es posible que desee que todos puedan probarla nuevamente.En el configurador, puede renovar la licencia de prueba para usuarios con licencias caducadas configurando un código de versión que debería activar esto.Por ejemplo, el usuario está ejecutando su aplicación en el código de versión 3 y desea que pruebe las funciones del código de versión 4.Si actualiza la aplicación o la reinstala, podrá volver a utilizar el período de prueba completo porque el servidor sabe en qué versión la probó la última vez.

  • Todo está bajo la licencia Apache 2.0.

La forma más fácil y mejor manera de hacer esto es poner en práctica los BackupSharedPreferences.

Las preferencias se conservan, incluso si la aplicación se desinstala y vuelve a instalar.

Simplemente guarda la fecha de instalación como una preferencia y que son buenos para ir.

Aquí está la teoría: http://developer.android.com/reference/android/app/ copia de seguridad / SharedPreferencesBackupHelper.html

Aquí está el ejemplo: SharedPreferences Android de copia de seguridad no funciona

Enfoque 4:. Usar la aplicación momento de la instalación

Desde nivel API 9 (Android 2.3.2, 2.3.1, Android 2.3, pan de jengibre) hay firstInstallTime y LastUpdateTime en PackageInfo.

Para leer más: Como llegar instalación de aplicaciones de tiempo desde androide

Ahora se ha añadido en la versión reciente de suscripción de prueba gratuita androide, se puede desbloquear todas las características de su aplicación sólo después de la compra de la suscripción dentro de la aplicación para un período de prueba gratuito. Esto le permitirá al usuario utilizar la aplicación para un período de prueba, si la aplicación todavía se desinstala después del período de prueba, el dinero de suscripción será transferido a usted. No he probado, pero simplemente compartir una idea.

Aquí hay documentación

En mi opinión, la mejor manera de hacer esto es simplemente usar la base de datos en tiempo real Firebase:

1) Añadir soporte Firebase a su aplicación

2) Seleccione 'autenticación anónima' para que el usuario no tiene que registrarse o incluso saber lo que estás haciendo. Esto está garantizado para vincular a la cuenta de usuario autenticado actualmente y así funcionará en todos los dispositivos.

3) Usar la API de base de datos en tiempo real para establecer un valor de 'installed_date'. En el momento del lanzamiento, sólo tiene que recuperar este valor y utilizar esto.

Yo he hecho lo mismo y funciona muy bien. Pude probar esto a través de desinstalar / reinstala y el valor en la base de datos en tiempo real sigue siendo el mismo. De esta manera su período de prueba funciona a través de múltiples dispositivos de usuario. Puede incluso su versión install_date de manera que la aplicación 'restablece' la fecha del juicio para cada nueva versión principal.

Actualizar : Después de probar un poco más, parece anónima Firebase parece asignar un ID diferente en caso de que tenga diferentes dispositivos y no se garantiza entre vuelve a instalar: / La única forma garantizada es utilizar Firebase pero lo atan a su cuenta de Google. Esto debería funcionar, pero requeriría un paso adicional donde el usuario primero tiene que iniciar sesión / registro.

he terminado hasta ahora con un enfoque ligeramente menos elegante de la simple comprobación contra las preferencias de copia de seguridad y una fecha almacenada en las preferencias después de la instalación. Esto funciona para aplicaciones centradas en datos, donde no tiene sentido para una persona para volver a instalar la aplicación y volver a introducir todos los datos previamente agregados, pero no funcionaría para un juego sencillo.

Después de mirar todas las opciones en este y otros hilos, estos son mis hallazgos.

Preferencias compartidas, base de datos.Se puede borrar en la configuración de Android y se pierde después de reinstalar una aplicación. Se puede realizar una copia de seguridad con el mecanismo de copia de seguridad de Android. y se restaurará después de una reinstalación.Es posible que la copia de seguridad no siempre esté disponible, aunque debería estarlo en la mayoría de los dispositivos.

Almacenamiento externo (escribir en un archivo)No se ve afectado por un borrado de la configuración o una reinstalación si no escriba en el directorio privado de la aplicación.Pero: requiere que le pida permiso al usuario en tiempo de ejecución en versiones más nuevas de Android, por lo que esto probablemente solo sea factible si necesita ese permiso de todos modos.También se puede realizar una copia de seguridad.

PackageInfo.firstInstallTimeSe restablece después de una reinstalación pero es estable en todas las actualizaciones.

Iniciar sesión en alguna cuentaNo importa si es su cuenta de Google a través de Firebase o una en su propio servidor:el juicio está ligado a la cuenta.Crear una nueva cuenta restablecerá la prueba.

Inicio de sesión anónimo de FirebasePuede iniciar sesión como usuario de forma anónima y almacenar sus datos en Firebase.Pero aparentemente una reinstalación de la aplicación y tal vez otros eventos no documentados pueden darle al usuario una nueva identificación anónima, restableciendo su tiempo de prueba.(El propio Google no proporciona mucha documentación sobre esto)

ANDROID_ID Puede no estar disponible y puede cambiar bajo ciertas circunstancias, por ejemplo, restablecimiento de fábrica.Las opiniones sobre si es una buena idea utilizar esto para identificar dispositivos parecen diferir.

Reproducir ID de publicidadPuede ser restablecido por el usuario. El usuario puede desactivarlo al optar por no participar en el seguimiento de anuncios.

ID de instancia Restablecer en una reinstalación. Restablecer en caso de un evento de seguridad.Puede restablecerse mediante su aplicación.

Qué (combinación de) métodos funciona para usted depende de su aplicación y de cuánto esfuerzo cree que pondrá el John promedio para obtener otro período de prueba.Recomendaría evitar el uso solo Firebase anónimo y Advertising ID debido a su inestabilidad.Parece que un enfoque multifactorial dará los mejores resultados.Los factores que están disponibles para usted dependen de su aplicación y sus permisos.

Para mi propia aplicación, encontré que las preferencias compartidas + firstInstallTime + copia de seguridad de las preferencias son el método menos intrusivo pero también lo suficientemente efectivo.Debe asegurarse de solicitar una copia de seguridad solo después de verificar y almacenar la hora de inicio de la prueba en las preferencias compartidas.Los valores de las preferencias compartidas deben tener prioridad sobre firstInstallTime.Luego, el usuario tiene que reinstalar la aplicación, ejecutarla una vez y luego borrar los datos de la aplicación para restablecer la versión de prueba, lo cual supone bastante trabajo.Sin embargo, en dispositivos sin transporte de respaldo, el usuario puede restablecer la versión de prueba simplemente reinstalándola.

He hecho que ese enfoque esté disponible como una biblioteca extensible.

Por definición, todos aplicaciones de pago en el mercado Android se puede evaluar durante 24 horas después de la compra.

Hay un botón 'Desinstalar y reembolso' que cambia a 'Desinstalar' después de 24 horas.

Yo diría este botón es demasiado prominente!

me encuentro con esta pregunta durante la búsqueda de un mismo problema, creo que podemos utilizar la fecha API libre como http : //www.timeapi.org/utc/now o alguna otra fecha API para comprobar expiración de aplicación rastro. De esta manera es eficiente si desea entregar el demo y preocupado por el pago y necesita fijar demostración tenencia. :)

encontrar el siguiente código

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

}
}

su solución de trabajo .....

Así es como hice la mía, creé 2 aplicaciones una con actividad de prueba sin el otro sin,

Subí la que no tenía actividad de prueba a Play Store como aplicación paga,

y el que tiene actividad de prueba como aplicación gratuita.

La aplicación gratuita en el primer lanzamiento tiene opciones para la compra de prueba y tienda, si el usuario selecciona la tienda, compra que redirige a la tienda para que el usuario la compre, pero si el usuario hace clic en prueba, los lleva a la actividad de prueba

NÓTESE BIEN:Usé la opción 3 como @snctln pero con modificaciones.

primero, no dependí de la hora del dispositivo, obtuve mi hora del archivo php que realiza el registro de prueba en la base de datos,

en segundo lugar, utilicé el número de serie del dispositivo para identificar de forma única cada dispositivo,

por último, la aplicación depende del valor de tiempo devuelto por la conexión del servidor, no de su propio tiempo, por lo que el sistema solo se puede eludir si se cambia el número de serie del dispositivo, lo cual es bastante estresante para el usuario.

así que aquí va mi código (para la actividad de prueba):

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


}

Mi archivo php se ve así (es una tecnología 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;
        }

    }

luego, en la actividad principal, uso la preferencia compartida (fecha de instalación creada en la actividad de prueba) para monitorear la cantidad de días restantes y, si los días terminaron, bloqueo la interfaz de usuario de la actividad principal con un mensaje que los lleva a la tienda para comprar.

El único inconveniente que veo aquí es que si un Usuario deshonesto compra la aplicación paga y decide compartirla con aplicaciones como Zender, compartir archivos o incluso alojar el archivo apk directamente en un servidor para que la gente lo descargue de forma gratuita.Pero estoy seguro de que pronto editaré esta respuesta con una solución o un enlace a la solución.

Espero que esto salve un alma... algún día.

Feliz codificación...

@snctln 3 se puede hacer fácilmente añadiendo un archivo PHP en un servidor web con PHP y MySQL instalado como muchos de ellos tienen.

Desde el lado de Android un identificador (ID de dispositivo, Google cuenta o lo que quieras) se pasa como argumento a la URL usando HttpURLConnection y el php devuelve la fecha de la primera instalación de si existe en la tabla o se inserta una nueva fila y se devuelve la fecha actual.

Funciona bien para mí.

Si tengo tiempo voy a publicar algo de código!

Buena suerte!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top