versión una tus todos tener que producción produccion play pega para nuevos novedades notas las lanzamiento iniciar ingresa hayan hacer google esta desactivado comprueba borrador añadido asegúrate aquí alfa android trialware android-1.5-cupcake

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



novedades de esta versión (13)

Tengo una aplicación que quiero lanzar al mercado como una aplicación de pago. 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?


Actualmente, la mayoría de los desarrolladores lo logran utilizando una de las siguientes 3 técnicas.

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

El segundo enfoque es más difícil de eludir, pero aún se puede evitar. Usa una bomba de tiempo codificada. Básicamente con este enfoque, será un código difícil una fecha de finalización para la versión de prueba, y todos los usuarios que descarguen y usen la aplicación dejarán de poder usar la aplicación al mismo tiempo. He utilizado este enfoque porque es fácil de implementar y, en general, no me apetecía pasar por la tercera técnica. Los usuarios pueden eludir esto al cambiar manualmente la fecha en su teléfono, pero la mayoría de los usuarios no tendrán la molestia de hacer tal cosa.

La tercera técnica es la única forma en que he escuchado para poder realmente lograr lo que quieres hacer. Deberá configurar un servidor, y cada vez que inicie su aplicación, su aplicación enviará el identificador único del teléfono al servidor. Si el servidor no tiene una entrada para esa identificación de teléfono, entonces crea una nueva y anota la hora. Si el servidor tiene una entrada para la identificación del teléfono, entonces realiza una simple comprobación para ver si el período de prueba ha expirado. Luego, comunica los resultados del control de vencimiento de prueba nuevamente a su aplicación. Este enfoque no debe ser evitable, pero sí requiere la configuración de un servidor web y tal.

Siempre es una buena práctica hacer estos controles en onCreate. Si la expiración ha finalizado, aparecerá un AlertDialog con un enlace de mercado a la versión completa de la aplicación. Solo incluya un botón "Aceptar" y, una vez que el usuario haga clic en "Aceptar", realice una llamada a "finalizar ()" para finalizar la actividad.


Ahora en la versión reciente de la suscripción de prueba gratuita de Android se ha agregado, puede desbloquear todas las funciones de su aplicación solo después de comprar la suscripción dentro de la aplicación durante un período de prueba gratuito. Esto le permitirá al usuario usar su aplicación por un período de prueba, si la aplicación todavía se desinstala después del período de prueba, el dinero de la suscripción se le transferirá. No lo he intentado, pero solo compartí una idea.

Aquí hay documentación


Así es como fui con el mío, creé 2 aplicaciones, una con actividad de prueba y la otra sin,

Cargué el que no tiene actividad de prueba para jugar en la tienda 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 comprar tienda, la redirecciona a la tienda para que el usuario la compre, pero si el usuario hace clic en la prueba, los lleva a la actividad de prueba.

NB: utilicé la opción 3 como @snctln pero con modificaciones

primero , no dependí del tiempo del dispositivo, obtuve mi tiempo del archivo php que hace el registro de prueba en el db,

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 del 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 utilizo la preferencia compartida (fecha de instalación creada en la actividad de prueba) para monitorear el número de días restantes y si los días terminan, bloqueo la IU 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 de Rogue compra la aplicación de pago y decide compartir con aplicaciones como Zender, compartir archivos o incluso alojar el archivo apk directamente en un servidor para que las personas lo descarguen gratis. Pero estoy seguro de que pronto editaré esta respuesta con una solución para eso o un enlace a la solución.

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

Feliz codificación ...


Desarrollé un SDK de prueba de Android que simplemente puede incluir en su proyecto de Android Studio y se ocupará de toda la administración del lado del servidor (incluidos los períodos de gracia fuera de línea).

Para usarlo, simplemente

Agregue la biblioteca al build.gradle su módulo principal

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

Inicialice la biblioteca en el método onCreate() su actividad principal

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

Agregue un controlador 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, llame a mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback); Puedes encontrar la clave de tu aplicación y el SKU de prueba en el panel de desarrollo de Trialy .


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

Preferencias compartidas, base de datos Puede borrarse en la configuración de Android, perderse después de la reinstalación de una aplicación. Se puede hacer una copia de seguridad con el mecanismo de copia de seguridad de Android y se restaurará después de una reinstalación. La copia de seguridad puede no estar siempre disponible, aunque debería estar en la mayoría de los dispositivos

Almacenamiento externo (escritura en un archivo) No se ve afectado por un borrado de la configuración o una reinstalación si no escribimos en el directorio privado de la aplicación . Pero: requiere que solicites permiso al usuario en tiempo de ejecución en las versiones de Android más nuevas, por lo que probablemente solo sea posible si necesitas ese permiso. También se puede hacer una copia de seguridad.

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

Iniciar sesión en una cuenta No importa si se trata de su cuenta de Google a través de Firebase o una en su propio servidor: la versión de prueba está vinculada a la cuenta. Hacer una nueva cuenta restablecerá la prueba.

Inicio de sesión anónimo de Firebase Puede iniciar sesión en un usuario de forma anónima y almacenar datos para ellos en Firebase. Pero aparentemente una reinstalación de la aplicación y quizás otros eventos no documentados pueden darle al usuario una nueva identificación anónima , restableciendo su tiempo de prueba. (Google mismo 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 usar esto para identificar dispositivos parecen diferir.

Play Advertising ID puede ser restablecido por el usuario. El usuario puede inhabilitarlo al inhabilitar el seguimiento de anuncios.

InstanceID Restablecer en una reinstalación . Restablecer en caso de un evento de seguridad. Puede ser restablecido por su aplicación.

¿Qué (combinación de) métodos funcionan para usted? Depende de su aplicación y de cuánto esfuerzo cree que el promedio de John pondrá en ganar otro período de prueba. Recomiendo evitar el uso exclusivo de Firebase y Advertising ID anónimos debido a su inestabilidad. Parece que un enfoque multifactor dará los mejores resultados. Los factores disponibles dependen de su aplicación y sus permisos.

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

He hecho ese enfoque disponible como una biblioteca extensible .


En mi opinión, la mejor manera de hacerlo es simplemente usar la base de datos de Firebase Realtime:

1) Agrega soporte de Firebase a tu aplicación

2) Seleccione ''Autenticación anónima'' para que el usuario no tenga que registrarse ni siquiera saber lo que está haciendo. Está garantizado que se vinculará a la cuenta de usuario autenticada actualmente y, por lo tanto, funcionará en todos los dispositivos.

3) Use la API Realtime Database para establecer un valor para ''installed_date''. En el momento del lanzamiento, simplemente recupera este valor y usa esto.

He hecho lo mismo y funciona muy bien. Pude probar esto en la desinstalación / reinstalación y el valor en la base de datos en tiempo real sigue siendo el mismo. De esta manera, su período de prueba funciona en múltiples dispositivos de usuario. Incluso puedes versionar tu install_date para que la aplicación ''restablezca'' la fecha de prueba para cada nueva versión principal.

ACTUALIZACIÓN : después de probar un poco más, parece que Firebase anónimo parece asignar una ID diferente en caso de que tengas diferentes dispositivos y no esté garantizado entre reinstalaciones: / La única forma garantizada es utilizar Firebase pero vincularlo a su google cuenta. Esto debería funcionar, pero requeriría un paso adicional donde el usuario primero necesita iniciar sesión / registrarse.

Hasta ahora he terminado con un enfoque un poco menos elegante de simplemente verificar contra las preferencias respaldadas y una fecha almacenada en las preferencias durante la instalación. Esto funciona para aplicaciones centradas en datos donde no tiene sentido que una persona vuelva a instalar la aplicación y vuelva a ingresar todos los datos agregados previamente, pero no funcionaría para un juego simple.


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

En caso de que quiera ir con el enfoque más simple (que 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 inspiraron 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 lo que piensas al respecto. ¿Usarías tal sistema? ¿Le gustaría verlo como un servicio en la nube (no tiene problemas con la configuración de un servidor)? ¿Preocupado por cuestiones de seguridad o razones de estabilidad? Apenas terminé el procedimiento de licenciatura, quiero seguir trabajando en el software. ¡Así que ahora es el momento en que necesito tus comentarios!

Sourcecode 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 es compatible con pruebas basadas en el tiempo y utiliza su cuenta (play store u otra) en lugar de una identificación de teléfono.

  • 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 de AES. Puede configurar la hora válida de la memoria caché con el configurador. Si un usuario "borra datos", la biblioteca forzará una verificación en el servidor.

  • El servidor está utilizando https y también digital firmando la respuesta de verificación de licencia. También tiene una API para aplicaciones de prueba CRUD y usuarios (editor y desarrollador). Similar a la verificación de licencias Los desarrolladores de la biblioteca pueden probar su implementación de comportamiento en la aplicación de prueba con el resultado de la prueba. Entonces, en el configurador puede establecer explícitamente la respuesta de su licencia como "con licencia", "sin licencia" o "error del servidor".

  • Si actualizas tu aplicación con una nueva característica que patea el culo, es posible que desees que todos puedan volver a intentarlo. En el configurador puede renovar la licencia de prueba para usuarios con licencias caducadas estableciendo un código de versión que debe desencadenar esto. Por ejemplo, el usuario está ejecutando su aplicación en el código de versión 3 y quiere que pruebe las características del código de versión 4. Si actualiza la aplicación o la reinstala, puede volver a utilizar el período de prueba completo porque el servidor sabe en qué versión lo ha intentado la última vez hora.

  • Todo está bajo la licencia de Apache 2.0



Me encuentro con esta pregunta mientras busco el mismo problema, creo que podemos utilizar la API de fecha gratuita como http://www.timeapi.org/utc/now o alguna otra fecha de API para verificar la caducidad de la aplicación de ruta. De esta manera es eficiente si desea entregar la demostración y está preocupado por el pago y requiere una demo de fijación de la tenencia. :)

encuentra el código a continuación

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 .....


Por definición, todas las aplicaciones pagas de Android en el mercado se pueden evaluar durante 24 horas después de la compra.

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

Yo diría que este botón es demasiado prominente.



@snctln opción 3 se puede hacer fácilmente agregando un archivo php a un servidor web con php y mysql instalados como muchos de ellos tienen.

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

Funciona bien para mí.

¡Si tengo tiempo, publicaré un código!

Buena suerte !