Cómo agregar una verificación de licencia del cliente a tu app

Advertencia: Cuando tu app realiza el proceso de verificación de licencia del cliente, es más fácil para los atacantes potenciales modificar o quitar la lógica asociada con este proceso de verificación.

Por este motivo, te recomendamos que realices la verificación de licencia en el servidor.

Después de configurar una cuenta de publicador y un entorno de desarrollo (consulta Cómo realizar la configuración para licencias), tendrás todo listo para agregar la verificación de licencias a tu app con la biblioteca de verificación de licencias (LVL).

Agregar la verificación de licencia con la LVL implica las siguientes tareas:

  1. Agrega el permiso de licencia al manifiesto de tu aplicación.
  2. Implementa una política. Puedes elegir una de las implementaciones completas proporcionadas en la LVL o crear una propia.
  3. Implementa un ofuscador (si tu Policy va a almacenar en caché datos de respuesta de licencia).
  4. Agrega código para verificar la licencia en el elemento Activity principal de tu aplicación.
  5. Implementa un elemento DeviceLimiter (es opcional y no se recomienda para la mayoría de las aplicaciones).

En las siguientes secciones, se describen estas tareas. Cuando hayas terminado la integración, deberías poder compilar tu aplicación con éxito y comenzar a probarla, como se describe en Configura el entorno de prueba.

Para obtener una descripción general del conjunto completo de archivos de origen incluidos en la LVL, consulta Resumen de interfaces y clases de la LVL.

Cómo agregar el permiso de licencia

A fin de usar la aplicación de Google Play para enviar una verificación de licencia al servidor, tu aplicación debe solicitar el permiso correspondiente: com.android.vending.CHECK_LICENSE. Si tu aplicación no declara el permiso de licencia, pero intenta iniciar una verificación de licencia, la LVL genera una excepción de seguridad.

Para solicitar el permiso de licencia en tu aplicación, declara un elemento <uses-permission> como elemento secundario de <manifest>, de la siguiente manera:

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

Por ejemplo, así es como la aplicación de ejemplo de la LVL declara el permiso:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...">
    <!-- Devices >= 3 have version of Google Play that supports licensing. -->
    <uses-sdk android:minSdkVersion="3" />
    <!-- Required permission to check licensing. -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    ...
</manifest>

Nota: Actualmente, no puedes declarar el permiso CHECK_LICENSE en el manifiesto del proyecto de la biblioteca LVL, ya que las herramientas del SDK no lo fusionarán en los manifiestos de las aplicaciones dependientes. En cambio, debes declarar el permiso en el manifiesto de cada aplicación dependiente.

Cómo implementar una política

El servicio de licencias de Google Play no determina por sí mismo si un usuario específico con una licencia determinada debería tener acceso a tu aplicación. En cambio, esa tarea es responsabilidad de una implementación de Policy que debes proporcionar en tu aplicación.

La política es una interfaz declarada por la LVL que está diseñada para mantener la lógica de tu aplicación con el fin de permitir o rechazar el acceso del usuario en función del resultado de una verificación de licencia. Para usar la LVL, tu aplicación debe proporcionar una implementación de Policy.

La interfaz de Policy declara dos métodos, allowAccess() y processServerResponse(), que una instancia de LicenseChecker llama al procesar una respuesta del servidor de licencias. También declara una enumeración denominada LicenseResponse, que especifica el valor de respuesta de la licencia transmitido en las llamadas a processServerResponse().

  • processServerResponse() te permite preprocesar los datos de respuesta sin procesar recibidos del servidor de licencias antes de determinar si se otorga o no el acceso.

    Una implementación típica extraería algunos o la totalidad de los campos de la respuesta de licencia y almacenaría los datos localmente en un almacén persistente, como a través del almacenamiento SharedPreferences, para garantizar que los datos sean accesibles a través de invocaciones de aplicaciones y reinicios del dispositivo. Por ejemplo, una Policy mantendría la marca de tiempo de la última verificación de licencia exitosa, el recuento de reintentos, el período de validez de la licencia y la información similar en un almacén persistente, en lugar de restablecer los valores cada vez que se inicie la aplicación.

    Cuando se almacenan datos de respuesta localmente, Policy debe asegurarse de que los datos estén ofuscados (consulta Cómo implementar un ofuscador, a continuación).

  • allowAccess() determina si se otorga acceso al usuario para tu aplicación según los datos de respuesta de licencia disponibles (del servidor de licencias o de la caché) u otra información específica de la aplicación. Por ejemplo, tu implementación de allowAccess() podría tener en cuenta criterios adicionales, como el uso o diferentes datos recuperados de un servidor de backend. En todos los casos, una implementación de allowAccess() solo debe mostrar true si el usuario tiene licencia para usar la aplicación según lo determinado por el servidor de licencias o si hay un problema transitorio o de red que impide que se complete la verificación de licencias. En esos casos, tu implementación puede mantener un recuento de las respuestas de reintentos y permitir el acceso provisional hasta que se complete la siguiente verificación de la licencia.

Para simplificar el proceso de agregar licencias a tu aplicación y mostrarte cómo se debe diseñar una Policy, la LVL incluye dos implementaciones completas de Policy que puedes adaptar a tus necesidades o usar sin modificar:

  • ServerManagedPolicy, un elemento Policy flexible que usa configuraciones proporcionadas por el servidor y respuestas almacenadas en caché para administrar el acceso en diversas condiciones de red.
  • StrictPolicy, que no almacena en caché ningún dato de respuesta y permite el acceso únicamente si el servidor muestra una respuesta con licencia.

Para la mayoría de las aplicaciones, se recomienda el uso de ServerManagedPolicy. ServerManagedPolicy es el valor predeterminado de la LVL y está integrado con la aplicación de ejemplo de la LVL.

Lineamientos para políticas personalizadas

En tu implementación de licencia, puedes usar una de las políticas completas proporcionadas en la LVL (ServerManagedPolicy o StrictPolicy) o puedes crear una política personalizada. Para cualquier tipo de política personalizada, existen varios puntos de diseño importantes que debes comprender y tener en cuenta en tu implementación.

El servidor de licencias aplica límites de solicitudes generales para evitar el uso excesivo de recursos que podrían derivar en la denegación del servicio. Cuando una aplicación excede el límite de solicitud, el servidor de licencias muestra una respuesta 503, que se transfiere a tu aplicación como un error general del servidor. Esto significa que no habrá respuesta de licencia disponible para el usuario hasta que se restablezca el límite, lo que puede afectar al usuario por un período indefinido.

Si estás diseñando una política personalizada, te recomendamos lo siguiente con respecto a la Policy:

  1. Almacena en caché (y ofusca adecuadamente) la respuesta de licencia exitosa más reciente en el almacenamiento local persistente.
  2. Muestra la respuesta almacenada en caché para todas las verificaciones de licencia, siempre que la respuesta en caché sea válida, en lugar de enviar una solicitud al servidor de licencias. Se recomienda configurar la validez de la respuesta de acuerdo con el elemento VT adicional proporcionado por el servidor. Consulta Adicionales de respuesta del servidor para obtener más información.
  3. Utiliza un período de retirada exponencial para reintentar cualquier solicitud que haya obtenido errores. Ten en cuenta que el cliente de Google Play reintenta automáticamente las solicitudes fallidas, por lo que, en la mayoría de los casos, no es necesario que la Policy las reintente.
  4. Proporciona un "período de gracia" que permite al usuario acceder a tu aplicación por un tiempo o una cantidad de usos limitados mientras se revisa una licencia. El período de gracia beneficia al usuario porque permite el acceso hasta que se pueda completar con éxito la próxima verificación de licencia y te beneficia porque establece un límite estricto en el acceso a tu aplicación cuando no hay una respuesta de licencia válida disponible.

Es fundamental diseñar tu Policy de acuerdo con los lineamientos mencionados anteriormente, ya que eso garantiza la mejor experiencia posible de los usuarios y te brinda un control eficaz sobre tu aplicación incluso en condiciones de error.

Ten en cuenta que cualquier Policy puede usar la configuración proporcionada por el servidor de licencias para ayudar a administrar la validez y el almacenamiento en caché, el período de gracia de los reintentos y más. La extracción de la configuración proporcionada por el servidor es sencilla y se recomienda usarla. Consulta la implementación de ServerManagedPolicy para ver un ejemplo de cómo extraer y usar los adicionales. Para obtener una lista de la configuración del servidor y la información sobre cómo usarla, consulta Adicionales de respuesta del servidor.

ServerManagedPolicy

La LVL incluye una implementación completa y recomendada de la interfaz de Policy llamada ServerManagedPolicy. La implementación está integrada con las clases de la LVL y sirve como el valor predeterminado de Policy en la biblioteca.

ServerManagedPolicy proporciona todo el manejo de la licencia y las respuestas de reintento. Almacena en caché todos los datos de respuesta localmente en un archivo SharedPreferences y los ofusca con la implementación de Obfuscator de la aplicación. Esto garantiza que los datos de respuesta de licencia sean seguros y persistan durante los reinicios del dispositivo. ServerManagedPolicy proporciona implementaciones concretas de los métodos de interfaz processServerResponse() y allowAccess(), y también incluye un conjunto de métodos y tipos de compatibilidad para administrar las respuestas de licencia.

Es importante destacar que una característica clave de ServerManagedPolicy es que usa la configuración proporcionada por el servidor como base para administrar licencias durante el período de reembolso de una aplicación y por condiciones de error y red variables. Cuando una aplicación se comunica con el servidor de Google Play para verificar una licencia, el servidor agrega varias configuraciones como pares clave-valor en el campo de adicionales de algunos tipos de respuesta de licencia. Por ejemplo, el servidor proporciona valores recomendados para el período de validez de la licencia de la aplicación, el período de gracia de los reintentos y el recuento de reintentos máximos permitidos, entre otros. ServerManagedPolicy extrae los valores de la respuesta de licencia en su método processServerResponse() y los verifica en su método allowAccess(). Para obtener una lista de la configuración proporcionada por el servidor utilizada por ServerManagedPolicy, consulta Adicionales de respuesta del servidor.

Por motivos prácticos y para obtener un mejor rendimiento y la ventaja de usar la configuración de licencias del servidor de Google Play, te recomendamos usar ServerManagedPolicy como tu Policy de licencia.

Si te preocupa la seguridad de los datos de respuesta de licencia que se almacenan localmente en SharedPreferences, puedes usar un algoritmo de ofuscación más fuerte o diseñar un objeto Policy más estricto que no almacene datos de licencia. La LVL incluye un ejemplo de este tipo de Policy; consulta StrictPolicy para obtener más información.

Para usar ServerManagedPolicy, simplemente impórtala a tu Activity, crea una instancia y pasa una referencia a la instancia cuando construyas tu LicenseChecker. Consulta Cómo crear una instancia de LicenseChecker y LicenseCheckerCallback para obtener más información.

StrictPolicy

La LVL incluye una implementación completa alternativa de la interfaz de Policy llamada StrictPolicy. La implementación de StrictPolicy proporciona una política más restrictiva que ServerManagedPolicy, ya que no permite que el usuario acceda a la aplicación, a menos que se reciba en el momento del acceso una respuesta de licencia del servidor que indique que el usuario tiene licencia.

La principal característica de StrictPolicy es que no almacena ningún dato de respuesta de licencia localmente, en un almacenamiento persistente. Debido a que no se almacenan datos, no se realiza un seguimiento de las solicitudes de reintento y no se pueden usar las respuestas en caché para completar las verificaciones de licencia. Policy permite el acceso solo si sucede lo siguiente:

  • Se recibe la respuesta de licencia del servidor de licencias.
  • La respuesta de licencia indica que el usuario tiene licencia para acceder a la aplicación.

El uso de StrictPolicy es apropiado si tu principal preocupación es garantizar que, en todos los casos posibles, ningún usuario podrá acceder a la aplicación a menos que se confirme que tiene licencia en el momento del uso. Además, la política ofrece un poco más de seguridad que ServerManagedPolicy: como no se almacenan datos en caché localmente, no hay forma de que un usuario malintencionado pueda alterar los datos almacenados en caché y obtener acceso a la aplicación.

Al mismo tiempo, esta Policy presenta un desafío para los usuarios normales, ya que significa que no podrán acceder a la aplicación cuando no haya una conexión de red (móvil o Wi-Fi) disponible. Otro efecto secundario es que tu aplicación enviará más solicitudes de verificación de licencia al servidor, ya que no es posible usar una respuesta en caché.

En general, esta política sacrifica un poco la comodidad del usuario por una seguridad y un control absolutos sobre el acceso. Considera estas ventajas y desventajas cuidadosamente antes de usar esta Policy.

Para usar StrictPolicy, simplemente impórtala a tu Activity, crea una instancia y transmítele una referencia cuando construyas tu LicenseChecker. Consulta Cómo crear una instancia de LicenseChecker y LicenseCheckerCallback para obtener más información.

Una implementación de Policy típica necesita guardar los datos de respuesta de una aplicación en un almacenamiento persistente de modo que se pueda acceder a ella desde invocaciones de aplicaciones y reinicios del dispositivo. Por ejemplo, una Policy mantendría la marca de tiempo de la última verificación de licencia exitosa, el recuento de reintentos, el período de validez de la licencia y la información similar en un almacenamiento persistente, en lugar de restablecer los valores cada vez que se inicie la aplicación. La Policy predeterminada incluida en la LVL, ServerManagedPolicy, almacena los datos de respuesta de la licencia en una instancia de SharedPreferences para garantizar que los datos sean persistentes.

Debido a que la Policy utilizará los datos de respuesta de licencia almacenados para determinar si se permite o no el acceso a la aplicación, debes garantizar que los datos almacenados sean seguros y que un usuario raíz no pueda reutilizarlos o manipularlos en un dispositivo. Específicamente, la Policy siempre debe ofuscar los datos antes de almacenarlos mediante una clave que sea única para la aplicación y el dispositivo. La ofuscación mediante una clave específica de la aplicación y específica del dispositivo es fundamental, ya que evita que los datos ofuscados se compartan entre aplicaciones y dispositivos.

La LVL ayuda a la aplicación a almacenar los datos de respuesta de licencia de manera segura y persistente. En primer lugar, proporciona una interfaz de Obfuscator que permite que la aplicación proporcione el algoritmo de ofuscación de su elección para los datos almacenados. A partir de allí, la LVL proporciona la clase auxiliar PreferenceObfuscator, que maneja la mayor parte del trabajo de llamar a la clase Obfuscator de la aplicación y leer y escribir los datos ofuscados en una instancia de SharedPreferences.

La LVL proporciona una implementación de Obfuscator completa, llamada AESObfuscator, que utiliza encriptación AES para ofuscar datos. Puedes usar AESObfuscator en tu aplicación sin modificaciones o adaptarlo a tus necesidades. Si usas un elemento Policy (como ServerManagedPolicy) que almacena datos de respuesta de licencia en caché, te recomendamos usar AESObfuscator como base para tu implementación de Obfuscator. Para obtener más información, consulta la siguiente sección.

AESObfuscator

La LVL incluye una implementación completa y recomendada de la interfaz de Obfuscator llamada AESObfuscator. La implementación se integra con la aplicación de ejemplo de LVL y se configura como elemento Obfuscator predeterminado en la biblioteca.

AESObfuscator proporciona una ofuscación segura de datos mediante el uso de AES para encriptar y desencriptar los datos a medida que se escriben o se leen desde el almacenamiento. El elemento Obfuscator genera la encriptación mediante tres campos de datos proporcionados por la aplicación:

  1. Una sal, que es un arreglo de bytes aleatorios para usar en cada (des)ofuscación.
  2. Una string de identificación de aplicación, que generalmente es el nombre del paquete de la aplicación.
  3. Una string de identificación de dispositivo, derivada de tantas fuentes específicas de dispositivo como sea posible, para que sea única.

Para usar AESObfuscator, primero impórtalo a tu Activity. Declara un arreglo final estático privado para contener los bytes de sal y, luego, inicialízalo en 20 bytes generados aleatoriamente.

Kotlin

// Generate 20 random bytes, and put them here.
private val SALT = byteArrayOf(
        -46, 65, 30, -128, -103, -57, 74, -64, 51, 88,
        -95, -45, 77, -117, -36, -113, -11, 32, -64, 89
)

Java

...
    // Generate 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
     -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95,
     -45, 77, -117, -36, -113, -11, 32, -64, 89
     };
    ...

A continuación, declara una variable para que contenga un identificador de dispositivo y genera un valor para este de cualquier manera que sea necesaria. Por ejemplo, la aplicación de muestra incluida en la LVL consulta la configuración del sistema para android.Settings.Secure.ANDROID_ID, que es exclusiva de cada dispositivo.

Ten en cuenta que, según las API que utilices, es posible que tu aplicación deba solicitar permisos adicionales para adquirir información específica del dispositivo. Por ejemplo, para consultar el TelephonyManager a fin de obtener el IMEI del dispositivo o datos relacionados, la aplicación también deberá solicitar el permiso android.permission.READ_PHONE_STATE en su manifiesto.

Antes de solicitar nuevos permisos con el único propósito de adquirir información específica del dispositivo para tu Obfuscator, considera cómo podría afectar a tu aplicación o su filtrado en Google Play (ya que algunos permisos pueden hacer que las herramientas de compilación del SDK agreguen el objeto <uses-feature> asociado).

Por último, crea una instancia de AESObfuscator, pasa la sal, el identificador de la aplicación y el identificador del dispositivo. Puedes construir la instancia directamente mientras construyes tu Policy y tu LicenseChecker. Por ejemplo:

Kotlin

    ...
    // Construct the LicenseChecker with a Policy.
    private val checker = LicenseChecker(
            this,
            ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
            BASE64_PUBLIC_KEY
    )
    ...

Java

    ...
    // Construct the LicenseChecker with a Policy.
    checker = new LicenseChecker(
        this, new ServerManagedPolicy(this,
            new AESObfuscator(SALT, getPackageName(), deviceId)),
        BASE64_PUBLIC_KEY // Your public licensing key.
        );
    ...

Para ver un ejemplo completo, consulta MainActivity en la aplicación de ejemplo de LVL.

Cómo verificar la licencia desde un objeto Activity

Una vez que has implementado una Policy para administrar el acceso a tu aplicación, el siguiente paso es agregar una verificación de licencia a tu aplicación, que inicia una consulta al servidor de licencias si es necesario y administra el acceso a la aplicación en función de la respuesta de licencia. Todo el trabajo de agregar la verificación de licencia y manejar la respuesta se lleva a cabo en tu archivo de origen Activity principal.

Para agregar la verificación de licencia y manejar la respuesta, debes hacer lo siguiente:

  1. Agregar importaciones
  2. Implementar LicenseCheckerCallback como una clase interna privada
  3. Crear un controlador para publicar desde LicenseCheckerCallback en el subproceso de IU
  4. Crear una instancia de LicenseChecker y LicenseCheckerCallback
  5. Llamar a checkAccess() para iniciar la verificación de la licencia
  6. Incorporar tu clave pública en la licencia
  7. Llamar al método onDestroy() de LicenseChecker para cerrar las conexiones de IPC

En las secciones que aparecen a continuación, se describen estas tareas.

Descripción general de la verificación y respuesta de la licencia

En la mayoría de los casos, debes agregar la verificación de licencia al elemento Activity principal de tu aplicación, en el método onCreate(). Esto garantiza que, cuando el usuario inicie directamente tu aplicación, se invocará la verificación de licencia de inmediato. En algunos casos, también puedes agregar verificaciones de licencia en otras ubicaciones. Por ejemplo, si tu aplicación incluye varios componentes de Activity que otras aplicaciones pueden iniciar mediante Intent, puedes agregar verificaciones de licencia en esos objetos Activity.

Una verificación de licencia consta de las siguientes dos acciones principales:

  • Una llamada a un método para iniciar la verificación de licencia, en la LVL, esta es una llamada al método checkAccess() de un objeto LicenseChecker que puedes construir.
  • Una devolución de llamada que muestra el resultado de la verificación de la licencia. En la LVL, esta es una interfaz de LicenseCheckerCallback que debes implementar. La interfaz declara dos métodos, allow() y dontAllow(), que la biblioteca invoca en función del resultado de la verificación de la licencia. Implementa estos dos métodos con la lógica necesaria para permitir o impedir el acceso del usuario a tu aplicación. Ten en cuenta que estos métodos no determinan si permites el acceso; esa determinación es responsabilidad de tu implementación de Policy. Por el contrario, estos métodos simplemente proporcionan los comportamientos de la aplicación para saber cómo permitir y no permitir el acceso (y controlar los errores de la aplicación).

    Los métodos allow() y dontAllow() proporcionan un "motivo" para su respuesta, que puede ser uno de los valores de Policy: LICENSED, NOT_LICENSED o RETRY. En particular, debes manejar el caso en el que el método recibe la respuesta de RETRY para dontAllow() y proporcionarle al usuario un botón "Reintentar", que podría aparecer porque el servicio no estaba disponible durante la solicitud.

Figura 1: Descripción general de una interacción típica de verificación de licencias

En el diagrama anterior, se ilustra cómo se realiza una verificación de licencia típica:

  1. El código del objeto Activity principal de la aplicación crea instancias de objetos LicenseCheckerCallback y LicenseChecker. Cuando se construye LicenseChecker, el código pasa a Context, una implementación de Policy para usar y la clave pública de la cuenta del publicador que tiene como objetivo obtener licencias como parámetros.
  2. El código luego llama al método checkAccess() en el objeto LicenseChecker. La implementación del método llama a Policy a fin de determinar si hay una respuesta de licencia válida almacenada en caché localmente, en SharedPreferences.
    • Si es así, la implementación de checkAccess() llama a allow().
    • De lo contrario, LicenseChecker inicia una solicitud de verificación de licencia que se envía al servidor de licencias.

    Nota: El servidor de licencias siempre muestra LICENSED cuando se verifica la licencia de un borrador de aplicación.

  3. Cuando se recibe una respuesta, LicenseChecker crea un LicenseValidator que verifica los datos de licencia firmados y extrae los campos de la respuesta, y luego los pasa a tu Policy para una evaluación adicional.
    • Si la licencia es válida, Policy almacena en caché la respuesta de SharedPreferences y notifica al validador, que luego llama al método allow() en el objeto LicenseCheckerCallback.
    • Si no es válida, Policy notifica al validador, que llama al método dontAllow() en LicenseCheckerCallback.
  4. En caso de un error local o de servidor recuperable, como cuando la red no está disponible para enviar la solicitud, LicenseChecker pasa una respuesta de RETRY al método processServerResponse() del objeto Policy.

    Además, los métodos de devolución de llamada allow() y dontAllow() reciben un argumento reason. El motivo del método allow() suele ser Policy.LICENSED o Policy.RETRY, y el motivo de dontAllow() suele ser Policy.NOT_LICENSED o Policy.RETRY. Estos valores de respuesta son útiles porque permiten mostrar una respuesta apropiada al usuario, por ejemplo, proporcionando un botón "Reintentar" cuando dontAllow() responde con Policy.RETRY, lo que podría deberse a que el servicio no estaba disponible.

  5. En caso de un error de aplicación, como cuando la aplicación intenta verificar la licencia de un nombre de paquete no válido, LicenseChecker pasa una respuesta de error al método applicationError() de LicenseCheckerCallback.

Ten en cuenta que, además de iniciar la verificación de la licencia y controlar el resultado, lo que se describe en las siguientes secciones, tu aplicación también debe proporcionar una implementación de política y, si Policy almacena datos de respuesta (como ServerManagedPolicy), una implementación del ofuscador.

Cómo agregar importaciones

Primero, abre el archivo de clase del objeto Activity principal de la aplicación y, luego, importa LicenseChecker y LicenseCheckerCallback desde el paquete de la LVL.

Kotlin

import com.google.android.vending.licensing.LicenseChecker
import com.google.android.vending.licensing.LicenseCheckerCallback

Java

import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;

Si estás utilizando la implementación de Policy predeterminada que se proporciona con la LVL, ServerManagedPolicy, impórtala también junto con el AESObfuscator. Si estás utilizando una Policy o un Obfuscator personalizados, impórtalos.

Kotlin

import com.google.android.vending.licensing.ServerManagedPolicy
import com.google.android.vending.licensing.AESObfuscator

Java

import com.google.android.vending.licensing.ServerManagedPolicy;
import com.google.android.vending.licensing.AESObfuscator;

Cómo implementar LicenseCheckerCallback como una clase interna privada

LicenseCheckerCallback es una interfaz proporcionada por LVL para controlar el resultado de una verificación de licencia. Para admitir licencias mediante la LVL, debes implementar LicenseCheckerCallback y sus métodos con el objetivo de permitir o no el acceso a la aplicación.

El resultado de una verificación de licencia es siempre una llamada a uno de los métodos LicenseCheckerCallback, basada en la validación de la carga útil de respuesta, el código de respuesta del servidor en sí mismo y cualquier procesamiento adicional proporcionado por tu Policy. Tu aplicación puede implementar los métodos de cualquier forma que sea necesaria. En general, es mejor mantener los métodos simples y limitarlos a administrar el estado de la IU y el acceso a las aplicaciones. Si deseas agregar más procesamiento de las respuestas de licencia, por ejemplo, durante la comunicación con un servidor de fondo, o aplicar restricciones personalizadas, debes considerar incorporar ese código en tu Policy, en lugar de incluirlo en los métodos LicenseCheckerCallback.

En la mayoría de los casos, debes declarar tu implementación de LicenseCheckerCallback como una clase privada dentro de la clase del objeto Activity principal de tu aplicación.

Implementa los métodos allow() y dontAllow() según sea necesario. Para empezar, puedes usar comportamientos simples de administración de resultados en los métodos, como mostrar el resultado de la licencia en un cuadro de diálogo. Esto te servirá para ejecutar tu aplicación más rápido y te ayudará en la depuración. Más adelante, una vez que hayas determinado los comportamientos exactos que desees, podrás agregar un control más complejo.

Algunas sugerencias para controlar respuestas sin licencia en dontAllow() incluyen las siguientes:

  • Mostrar un diálogo "Reintentar" al usuario, que incluya un botón para iniciar una nueva verificación de licencia si el elemento reason proporcionado es Policy.RETRY.
  • Mostrar un diálogo "Comprar esta aplicación", que incluya un botón que vincule al usuario a la página de detalles de la aplicación en Google Play, desde donde el usuario pueda comprar la aplicación. Para obtener más información sobre cómo configurar esos vínculos, consulta Cómo vincular tus productos.
  • Muestra una notificación de aviso que indica que las funciones de la aplicación son limitadas porque no tiene licencia.

En el siguiente ejemplo, se muestra cómo la aplicación de ejemplo LVL implementa LicenseCheckerCallback con métodos que muestran el resultado de la verificación de licencia en un diálogo.

Kotlin

private inner class MyLicenseCheckerCallback : LicenseCheckerCallback {

    override fun allow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        // Should allow user access.
        displayResult(getString(R.string.allow))
    }

    override fun dontAllow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        displayResult(getString(R.string.dont_allow))

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY)
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET)
        }
    }
}

Java

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
    public void allow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        // Should allow user access.
        displayResult(getString(R.string.allow));
    }

    public void dontAllow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        displayResult(getString(R.string.dont_allow));

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY);
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET);
        }
    }
}

Además, debes implementar el método applicationError(), que la LVL llama para permitir que tu aplicación maneje errores que no se pueden recuperar. Para obtener una lista de estos errores, consulta Códigos de respuesta del servidor en la Referencia sobre licencias. Puedes implementar el método de cualquier forma que sea necesaria. En la mayoría de los casos, el método debe registrar el código de error y llamar a dontAllow().

Cómo crear un controlador para publicar desde LicenseCheckerCallback en el subproceso de IU

Durante una verificación de licencia, la LVL pasa la solicitud a la aplicación de Google Play, que controla la comunicación con el servidor de licencias. La LVL pasa la solicitud por la IPC asíncrona (mediante Binder) para que el procesamiento real y la comunicación de red no se realicen en una conversación administrada por la aplicación. De manera similar, cuando la aplicación de Google Play recibe el resultado, invoca un método de devolución de llamada por IPC, que, a su vez, se ejecuta en un grupo de subprocesos de IPC en el proceso de tu aplicación.

La clase LicenseChecker administra la comunicación IPC de tu aplicación con la aplicación de Google Play, que incluye la llamada que envía la solicitud y la devolución de llamada que recibe la respuesta. LicenseChecker también rastrea las solicitudes de licencias abiertas y administra sus tiempos de espera.

Para que pueda controlar los tiempos de espera correctamente y también procesar las respuestas entrantes sin afectar el subproceso de IU de tu aplicación, LicenseChecker genera un subproceso en segundo plano en la instanciación. En el subproceso, realiza todo el procesamiento de los resultados de la verificación de la licencia, ya sea que el resultado sea una respuesta recibida del servidor o un error de tiempo de espera. Al finalizar el procesamiento, la LVL llama a tus métodos LicenseCheckerCallback desde el subproceso en segundo plano.

Para tu aplicación, esto significa lo siguiente:

  1. Se invocarán tus métodos LicenseCheckerCallback, en muchos casos, desde un subproceso en segundo plano.
  2. Esos métodos no podrán actualizar el estado ni invocar ningún procesamiento en el subproceso de IU, a menos que crees un controlador en el subproceso de IU y se publiquen los métodos de devolución de llamada en el controlador.

Si quieres que tus métodos LicenseCheckerCallback actualicen el subproceso de IU, crea una instancia de Handler en el método onCreate() del objeto Activity principal, como se muestra a continuación. En este ejemplo, los métodos LicenseCheckerCallback de la aplicación de ejemplo de la LVL (ver arriba) llaman a displayResult() para actualizar el subproceso de IU mediante el método post() del controlador.

Kotlin

    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        handler = Handler()
    }

Java

    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        handler = new Handler();
    }

Luego, en tus métodos LicenseCheckerCallback, puedes usar los métodos del controlador para publicar objetos Runnable o Message en el controlador. A continuación, se muestra cómo la aplicación de ejemplo que se incluye en la LVL publica un objeto Runnable en un controlador del subproceso de IU con el fin de mostrar el estado de la licencia.

Kotlin

private fun displayResult(result: String) {
    handler.post {
        statusText.text = result
        setProgressBarIndeterminateVisibility(false)
        checkLicenseButton.isEnabled = true
    }
}

Java

private void displayResult(final String result) {
        handler.post(new Runnable() {
            public void run() {
                statusText.setText(result);
                setProgressBarIndeterminateVisibility(false);
                checkLicenseButton.setEnabled(true);
            }
        });
    }

Cómo crear instancias de LicenseChecker y LicenseCheckerCallback

En el método onCreate() del objeto Activity principal, crea instancias privadas de LicenseCheckerCallback y LicenseChecker. Primero debes crear una instancia de LicenseCheckerCallback, ya que deberás pasar una referencia a esa instancia cuando llames al constructor para LicenseChecker.

Cuando creas una instancia de LicenseChecker, debes pasar estos parámetros:

  • La aplicación de Context.
  • Una referencia a la implementación de Policy para utilizar en la verificación de la licencia. En la mayoría de los casos, se usa la implementación predeterminada de Policy que proporciona la LVL, ServerManagedPolicy.
  • La variable String que contiene la clave pública de tu cuenta de publicador para la licencia.

Si estás utilizando ServerManagedPolicy, no necesitarás acceder directamente a la clase, por lo que podrás crear una instancia en el constructor LicenseChecker, como se muestra en el siguiente ejemplo. Ten en cuenta que deberás pasar una referencia a una nueva instancia de ofuscador cuando construyas ServerManagedPolicy.

En el siguiente ejemplo, se muestra la creación de instancias de LicenseChecker y LicenseCheckerCallback del método onCreate() de una clase Activity.

Kotlin

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var licenseCheckerCallback: LicenseCheckerCallback
    private lateinit var checker: LicenseChecker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = MyLicenseCheckerCallback()

        // Construct the LicenseChecker with a Policy.
        checker = LicenseChecker(
                this,
                ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
                BASE64_PUBLIC_KEY // Your public licensing key.
        )
        ...
    }
}

Java

public class MainActivity extends Activity {
    ...
    private LicenseCheckerCallback licenseCheckerCallback;
    private LicenseChecker checker;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = new MyLicenseCheckerCallback();

        // Construct the LicenseChecker with a Policy.
        checker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY // Your public licensing key.
            );
        ...
    }
}

Ten en cuenta que LicenseChecker llama a los métodos LicenseCheckerCallback del subproceso de IU solamente si hay una respuesta de licencia válida en la caché local. Si la verificación de licencia se envía al servidor, las devoluciones de llamada siempre se originan en el subproceso en segundo plano, incluso para errores de red.

Cómo llamar a checkAccess() para iniciar la verificación de la licencia

En tu objeto Activity principal, agrega una llamada al método checkAccess() de la instancia de LicenseChecker. En la llamada, pasa una referencia a tu instancia de LicenseCheckerCallback como parámetro. Si necesitas manejar algún efecto especial de IU o administración de estado antes de la llamada, puede resultarte útil llamar a checkAccess() desde un método wrapper. Por ejemplo, la aplicación de la LVL invoca al elemento checkAccess() desde un método wrapper doCheck():

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Call a wrapper method that initiates the license check
        doCheck()
        ...
    }
    ...
    private fun doCheck() {
        checkLicenseButton.isEnabled = false
        setProgressBarIndeterminateVisibility(true)
        statusText.setText(R.string.checking_license)
        checker.checkAccess(licenseCheckerCallback)
    }

Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Call a wrapper method that initiates the license check
        doCheck();
        ...
    }
    ...
    private void doCheck() {
        checkLicenseButton.setEnabled(false);
        setProgressBarIndeterminateVisibility(true);
        statusText.setText(R.string.checking_license);
        checker.checkAccess(licenseCheckerCallback);
    }

Cómo incorporar tu clave pública en la licencia

Para cada aplicación, el servicio de Google Play genera automáticamente un par de claves públicas/privadas de RSA de 2,048 bits que se usa para las licencias y la facturación integrada. El par de claves está asociado de forma exclusiva con la aplicación. Aunque está asociado con la aplicación, el par de claves no es igual a la clave que utilizas para firmar tus aplicaciones (ni deriva de esta).

Google Play Console expone la clave pública para las licencias a cualquier programador que acceda a Play Console, pero mantiene la clave privada oculta para todos los usuarios en una ubicación segura. Cuando una aplicación solicita una verificación de licencia para una aplicación publicada en tu cuenta, el servidor de licencias firma la respuesta de licencia con la clave privada del par de claves de tu aplicación. Cuando la LVL recibe la respuesta, utiliza la clave pública proporcionada por la aplicación para verificar la firma de la respuesta de licencia.

A fin de agregar licencias a una aplicación, debes obtener la clave pública de tu aplicación para las licencias y copiarla en tu aplicación. Aquí te mostramos cómo encontrar la clave pública de tu aplicación para la licencia:

  1. Ve a Google Play Console y accede. Asegúrate de acceder a la cuenta desde la cual se publica (o se publicará) la aplicación que recibirá la licencia.
  2. En la página de detalles de la aplicación, busca el vínculo Services & APIs y haz clic en él.
  3. En la página Services & APIs, busca la sección Licensing & In-App Billing. La clave pública para las licencias se proporciona en el campo Your License Key For This Application.

Para agregar la clave pública a tu aplicación, simplemente, copia la string de clave del campo y pégala en tu aplicación como el valor de la variable de string BASE64_PUBLIC_KEY. Cuando copies, asegúrate de haber seleccionado la string de clave completa, sin omitir ningún carácter.

A continuación, puedes ver un ejemplo de la aplicación de ejemplo de LVL:

Kotlin

private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example
class LicensingActivity : AppCompatActivity() {
    ...
}

Java

public class MainActivity extends Activity {
    private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
    ...
}

Cómo llamar al método onDestroy() de tu LicenseChecker para cerrar las conexiones IPC

Por último, para permitir que la LVL borre antes de que tu aplicación de Context cambie, agrega una llamada al método onDestroy() de LicenseCheckerde la implementación de onDestroy() de tu objeto Activity. La llamada hace que LicenseChecker cierre correctamente cualquier conexión IPC abierta al servicio ILicensingService de la aplicación de Google Play y quita cualquier referencia local al servicio y al controlador.

Si no se llama al método onDestroy() de LicenseChecker, se pueden producir problemas durante el ciclo de vida de la aplicación. Por ejemplo, si el usuario cambia la orientación de la pantalla mientras está activa una verificación de licencia, se cancela la aplicación de Context. Si tu aplicación no cierra correctamente la conexión IPC de LicenseChecker, se bloqueará cuando se reciba la respuesta. Del mismo modo, si el usuario sale de tu aplicación mientras se está realizando una verificación de licencia, se bloqueará la aplicación cuando reciba la respuesta, a menos que haya llamado correctamente al método onDestroy() de LicenseChecker para desconectarse del servicio.

A continuación, puedes ver un ejemplo de la aplicación de ejemplo que se incluye en la LVL, donde mChecker es la instancia de LicenseChecker:

Kotlin

    override fun onDestroy() {
        super.onDestroy()
        checker.onDestroy()
        ...
    }

Java

    @Override
    protected void onDestroy() {
        super.onDestroy();
        checker.onDestroy();
        ...
    }

Si vas a ampliar o modificar LicenseChecker, es posible que también debas llamar al método finishCheck() de LicenseChecker para limpiar las conexiones IPC abiertas.

Cómo implementar un elemento DeviceLimiter

En algunos casos, es posible que desees que tu Policy limite la cantidad de dispositivos reales que pueden usar una sola licencia. Esto evitaría que un usuario mueva una aplicación con licencia a varios dispositivos y use la aplicación en esos dispositivos con el mismo ID de la cuenta. También evitaría que un usuario "comparta" la aplicación proporcionando la información de la cuenta asociada con la licencia a otras personas, que luego podrían acceder a esa cuenta en sus dispositivos y acceder a la licencia de la aplicación.

La LVL admite las licencias por dispositivo proporcionando una interfaz de DeviceLimiter, que declara un único método, allowDeviceAccess(). Cuando un elemento LicenseValidator controla una respuesta del servidor de licencias, llama a allowDeviceAccess() y pasa una string de ID de usuario extraída de la respuesta.

Si no deseas admitir la limitación del dispositivo, no es necesario que realices ninguna acción: la clase LicenseChecker usa automáticamente una implementación predeterminada llamada NullDeviceLimiter. Como su nombre lo indica, NullDeviceLimiter es una clase "no operativa" cuyo método allowDeviceAccess() simplemente muestra una respuesta LICENSED para todos los usuarios y dispositivos.

Precaución: Por los motivos que se indican a continuación, la licencia por dispositivo no se recomienda para la mayoría de las aplicaciones.

  • Requiere que proporciones un servidor de backend para administrar la asignación de usuarios y dispositivos.
  • Sin darte cuenta, se podría impedir el acceso de un usuario a una aplicación que se haya comprado legítimamente en otro dispositivo.

Cómo ofuscar tu código

A fin de garantizar la seguridad de tu aplicación, particularmente para una aplicación paga que utiliza licencias o restricciones y protecciones personalizadas, es muy importante ofuscar el código de tu aplicación. Ofuscar el código de forma adecuada hace que sea más difícil que un usuario malicioso descompile el código de bytes de la aplicación, lo modifique (por ejemplo, quitando la verificación de licencia) y vuelva a compilarlo.

Hay varios programas ofuscadores disponibles para las aplicaciones para Android, incluso ProGuard, que también ofrece funciones de optimización de código. El uso de ProGuard o un programa similar para ofuscar tu código es altamente recomendado para todas las aplicaciones que usan licencias de Google Play.

Cómo publicar una aplicación con licencia

Cuando termines de probar la implementación de tu licencia, tendrás todo listo para publicar la aplicación en Google Play. Sigue los pasos normales para preparar, firmar y publicar la aplicación.

Dónde obtener asistencia

Si tienes preguntas o dificultades para implementar o publicar contenido en tus aplicaciones, utiliza los recursos de asistencia que se enumeran en la tabla que aparece a continuación. Si diriges tus consultas al foro adecuado, podrás obtener más rápido la asistencia que necesitas.

Tabla 2: Recursos de asistencia para desarrolladores para el Servicio de licencias de Google Play

Tipo de asistencia Recurso Rango de temas
Problemas relacionados con las pruebas y la programación Grupos de Google: desarrolladores de Android Descarga e integración de la LVL, proyectos de biblioteca, preguntas de Policy, ideas de experiencia del usuario, manejo de respuestas, Obfuscator, IPC, configuración del entorno de prueba
Stack Overflow: http://stackoverflow.com/questions/tagged/android
Problemas con las cuentas, las publicaciones y la implementación Foro de ayuda de Google Play Cuentas de publicador, par de claves de licencia, cuentas de prueba, respuestas del servidor, respuestas de prueba, implementación de aplicaciones y resultados
Preguntas frecuentes sobre la asistencia para licencias de mercado
Seguimiento de problemas de LVL Herramienta de seguimiento de errores del proyecto de Marketlicensing Informes de errores y problemas relacionados específicamente con las implementaciones de interfaces y clases de código fuente de la LVL

Para obtener información general acerca de cómo publicar en los grupos mencionados anteriormente, consulta la sección Recursos de la comunidad en la página Recursos de asistencia para programadores.

Recursos adicionales

La aplicación de ejemplo incluida con la LVL proporciona un ejemplo completo de cómo iniciar una verificación de licencia y controlar el resultado, en la clase MainActivity.