Licencia Google para aplicaciones Android en entorno .NET con C#

Si estás desarrollando alguna aplicación para Android con C# y quieres subirla al Market quizás te interese usar el sistema de licencias de Google para mantener un mínimo control sobre tu software. Cuando lo hagas necesitarás la clase LicenseChecker, originalmente implementada en Java. Una solución al problema pasa por generar una dll para .NET con la clase Java original y otra sería reimplementarla en C#, que es lo que ha hecho Matthew Leibowitz. Puedes bajarte su librería en Github. El uso es muy sencillo y no difiere de su equivalente en Java. El software usado es Visual Studio 2010 con la extensión Monodroid de Xamarin.

Preparar el entorno de desarrollo.

Hemos terminado la aplicación, está firmada y nos hemos dado de alta en el Market. Los pasos para empezar a configurar la licencia son:

  • Accedemos a la configuración de la cuenta y en el apartado Licencia para pruebas debemos introducir el correo que vamos usar para comprobaciones, puede ser cualquiera.
  • Cambiamos la lista desplegable situada debajo Respuesta de licencia de pruebas a LICENSED (la respuesta se define para todas las aplicaciones de nuestra cuenta).
  • Creamos una máquina virtual Android con el AVD que será registrada con dicho correo. La versión mínima de API debe ser la 8 y se recomienda usar las Google API, no las Android.
  • Una vez arrancado el dispositivo emulador vamos a DevTools - AccountsTester. En el menú desplegable Cuenta de trabajo seleccionamos Google y pulsamos Añadir para introducir los datos de la cuenta.
  • Volvemos al Market, subimos nuestra aplicación y copiamos la clave de licencia que encontramos en la pestaña Servicios y APIs.

Preparar LicenseChecker.

Primero compilamos la librería a la versión  de Android que usemos en nuestro proyecto. Abrimos LicenseVerificationLibrary.csproj (dentro de la carpeta del mismo nombre), cambiamos la versión en las características del proyecto y recompilamos. La dll que usaremos estará en la carpeta /bin/Debug o /bin/Release, según la opción elegida. Volvemos al proyecto original y añadimos la librería generada: References - Agregar referencia - Examinar, y ya podemos usarla.

La clase Licencia.

La implementación es sencilla: extendemos el objeto base con el listener ILicenseCheckerCallback, creamos el constructor para inicializar y redefinimos 2 procedimientos: Allow(PolicyServerResponse) y DontAllow(PolicyServerResponse). Cuando se llama a CheckAccess() se vuelve por uno de los dos, examinando el parámetro recibido sabremos el resultado del test. En este caso vamos a implementar la política de licencia más simple (ServerManagedPolicy) por lo que el parámetro PolicyServerResponse puede ser Licensed, NotLicensed o Retry. El último caso se puede dar si hay un fallo en la conexión.

Al ser funciones asíncronas no podemos simplemente llamarlas y esperar a que terminen para conocer el resultado, por ello he implementado la clase con dos variables de funciones callback que son llamadas en caso de aceptarse o no la licencia. El procedimiento Allow() llama siempre a la función de acceso permitido y DontAllow() verifica primero el resultado.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using LicenseVerificationLibrary;
using LicenseVerificationLibrary.Obfuscator;
using LicenseVerificationLibrary.Policy;

using Uri = Android.Net.Uri;

namespace TestLicenseChecker
{
    public class Licencia : Java.Lang.Object, ILicenseCheckerCallback
    {
        // Clave pública de 64 bits
        private const string Base64PublicKey =
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqSEPO6frjPZ/qdSTT80dCBjsHZ"
            + "ouZGadBRwlg9g34ueC6j4F348dy0Xgo4NdKX39pSX1RNl0kGaxX6sg04bp4qx6RfwVyD1C"
            + "PSEYdWldkuAQ9aNaQZ/yq6V+lmrqaKfJJuh1olqtsK8VVnvJ48Q+VwkIaT5CXhqeRAyZRX"
            + "MEmEGPTNybSYVf5P90CxdSRwpae/wrt9rzuXOnfUhLKc9WmovRLQ8GzXYzhbNBzbWrK0NE"
            + "+iXdxDGOZPDQPiLEaU2KliaWOBGO+2Cx5MSXZ3Xlm7e0Yo3F4x8BpMDQHs+3RSYTEaMvQk"
            + "/t4sfMbA4xCzAP57cl6Ae6SbWU46mk+lqDeQIDAQAB";


        // 20 bytes aleatorios
        private static readonly byte[] Salt = new byte[] { 46, 65, 30, 128, 103, 57, 74, 64, 51, 88, 95, 45, 77, 117, 36, 113, 11, 32, 64, 89 };

        private LicenseChecker checker;
        private Context _context;
        public event Action LicenciaOK, LicenciaNo;

        public Licencia(Context context)
        {
            _context = context;
            LicenciaOK = null;
            LicenciaNo = null;
            // Se usa ANDROID_ID pero se recomienda una cadena más fuerte
            string deviceId = Android.Provider.Settings.Secure.GetString(context.ContentResolver, Android.Provider.Settings.Secure.AndroidId);

            // Se construye el LicenseChecker con la política ServerManagedPolicy
            var obfuscator = new AesObfuscator(Salt, context.PackageName, deviceId);
            var policy = new ServerManagedPolicy(context, obfuscator);
            this.checker = new LicenseChecker(context, policy, Base64PublicKey);
        }

        public void Check() // Se comprueba la licencia
        {
            this.checker.CheckAccess(this);
        }

        // Se permite el acceso
        public void Allow(PolicyServerResponse response)
        {
            if (LicenciaOK)
                LicenciaOK();
        }

        //  No se permite el acceso
        //  Este callback se dispara siempre en modo test
        //  Hay que comprobar el valor devuelto por PolicyServerResponse para conocer el estado de la licencia
        //  El caso Retry se produce si hay un fallo de conexión
        public void DontAllow(PolicyServerResponse response)
        {
            public event Action proc = null;
            switch (response)
            {
                case PolicyServerResponse.Retry:
                case PolicyServerResponse.Licensed: proc = LicenciaOK; break;
                case PolicyServerResponse.NotLicensed: proc = LicenciaNo; break;
                default:    proc = LicenciaNo;    break;
            }
            if (proc)
              proc();
        }

        //  Devolución de código de error al desarrollador en caso de haber algún error
        public void ApplicationError(CallbackErrorCode errorCode)
        {
            var errorString = _context.GetString(Resource.String.LicenciaError);
            Toast.MakeText(_context, errorString + " " + errorCode, ToastLength.Short).Show();
        }
    }
}
La forma de llamar a la clase es:

Licencia CheckLicencia = new Licencia(this);
CheckLicencia.LicenciaNo += () =>
{
   //Código en caso de que la licencia no se verifique
};
CheckLicencia.LicenciaOK += () =>
{
   //Código en caso de que la licencia se verifique
};
CheckLicencia.Check();

Se observa que sólo el procedimiento DontAllow() efectúa comprobaciones. Se debe a que en modo prueba siempre se devuelve la respuesta a través de este procedimiento, sea positiva o negativa. A efectos del ejemplo la respuesta Retry equivale a aceptar la licencia. Se recomienda también crear un deviceID lo más fuerte posible para el ofuscador aunque puede resultar una tarea bastante complicada: podria usarse un hash del IMEI por ejemplo, pero hay tabletas WiFi que no lo tienen.

Enlaces: Android.Play.ExpansionLibrary, LicenseChecker, Setting Up for License (Google), Preparing an  Application for Release (Xamarin)

No hay comentarios:

Publicar un comentario