Tenemos un proyecto en el que necesitamos utilizar uno de estos terminales tipo POS ( Point of sale / punto de venta ) que a nuestro parecer era bastante completo, ya que cuenta con su propia impresora térmica y de alguna forma están relacionados con Xiaomi.

El problema

En nuestro equipo apenas un par conoce Android nativo, por lo que para acelerar el desarrollo decidimos utilizar Xamarin Forms que es el que todos conocemos, sin embargo nos encontramos con el problema de que no nos fue posible hacer funcionar correctamente el generador de código en base de los AIDL que si funcionan correctamente en nativo, mostrándonos un error referente a

Android.Graphics.BitmapStub

Este código es generado, por lo que no debería tocarse, así que no hubo forma de hacerlo funcionar así.

La solución

Para solucionar este problema, opté por generar un AAR que ya tenga todas las funcionalidades que necesitamos para después importarlas al proyecto Xamarin Android.

Explicaré paso a paso como generar este AAR y después como utilizarlo en sus proyectos.

1. Crear un proyecto en Android studio

Procederemos primero a crear un proyecto limpio usando Android Studio:

No necesitaremos un activity, por lo que seleccionamos “Add No Activity” y “Next”

El nombre del proyecto no importa, por lo que podemos poner lo que queramos, ya que crearemos un módulo nuevo que será el que usaremos en Xamarin

Una vez el proyecto esté cargado, creamos un nuevo módulo con File => New => New Module

Y aquí es importante seleccionar “Android Library”

Pongan un paquete y nombre de módulo que sea conveniente, ya que será el que usarán tanto para importar como para exportar la librería

En el proyecto les habrá creado un módulo nuevo llamado sunmiv2 (o el nombre que hayan elegido)

En este proyecto es donde implementarán sus archivos AIDL, en este caso yo usaré los que la documentación de la Sunmi V2 me indica, como si se tratara de una implementación en Android

La documentación de mis AIDL dicen que la carpeta principal vaya a un lado de java y dentro las carpetas correspondientes al paquete de mis AIDL

Para que Android Studio genere las clases necesarias basado en los AIDL deberemos hacer Make Module sunmiv2 al módulo

Una vez hecho esto, ya podemos realizar la implementación de los servicios pertinentes basados en la definición de los AIDL que quieran utilizar

Dejaré un ejemplo con unas cuantas funciones implementadas, del servicio de impresión, donde cada uno deberá poner los métodos que vayan a utilizar:

package com.marcomaldonado.sunmiv2.services;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.os.IBinder;
import android.os.RemoteException;

import com.marcomaldonado.sunmiv2.contracts.SunmiCallback;

import woyou.aidlservice.jiuiv5.ICallback;
import woyou.aidlservice.jiuiv5.IWoyouService;

public class SunmiPrinter {
    private static final SunmiPrinter printer = new SunmiPrinter();
    private IWoyouService woyouService = null;
    private final String UNREACHABLE = "unreachable";

    private ServiceConnection connService = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            woyouService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            woyouService = IWoyouService.Stub.asInterface(service);
        }
    };

    private SunmiPrinter(){}


    public static SunmiPrinter getInstance() {
        return printer;
    }

    public void initPrinter(Context context){
        Intent intent=new Intent();
        intent.setPackage("woyou.aidlservice.jiuiv5");
        intent.setAction("woyou.aidlservice.jiuiv5.IWoyouService");
        context.startService(intent);
        context.bindService(intent, connService, Context.BIND_AUTO_CREATE);
    }

    // --------------------------------

    public void printText(String text, SunmiCallback callback) {
        if  (woyouService != null) {
            try {
                woyouService.printText(text, makeCallback(callback));
            } catch (RemoteException e) {
                e.printStackTrace();
                ThrowErrorCallback(callback);
            }
        }
        else {
            ThrowErrorCallback(callback);
        }
    }

    public void printBitmap(Bitmap bitmap, SunmiCallback callback) {
        if (woyouService != null) {
            try {
                woyouService.printBitmap(bitmap, makeCallback(callback));
            } catch (RemoteException e) {
                e.printStackTrace();
                ThrowErrorCallback(callback);
            }
        }
        else {
            ThrowErrorCallback(callback);
        }
    }

    private void ThrowErrorCallback(SunmiCallback callback) {
        if (callback != null) {
            try {
                callback.onRaiseException(500, UNREACHABLE);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    private ICallback makeCallback(final SunmiCallback callback) {
        if (callback != null) {
            return new ICallback.Stub() {
                @Override
                public void onRunResult(boolean isSuccess) throws RemoteException {
                    callback.onRunResult(isSuccess);
                }

                @Override
                public void onReturnString(String result) throws RemoteException {
                    callback.onReturnString(result);
                }

                @Override
                public void onRaiseException(int code, String msg) throws RemoteException {
                    callback.onRaiseException(code, msg);
                }
            };
        }
        return null;
    }


}
package com.marcomaldonado.sunmiv2.contracts;

import android.os.RemoteException;

public interface SunmiCallback {
    void onRunResult(boolean isSuccess) throws RemoteException;
    void onReturnString(String result) throws RemoteException;
    void onRaiseException(int code, String msg) throws RemoteException;
}

La interfaz SunmiCallback y la implementación completa de este SunmiPrinter la pueden encontrar en este gist:

https://gist.github.com/Mxrck/297290b47c6a70392148b59f3ae3bcf2

Una vez que tengan hecha la implementación, tendremos que construir nuestro AAR, para hacerlo solo es necesario llamar de nuevo a Make Module Sunmi y buscamos el archivo generado;

El modo de compilación lo tengo en release, por lo que mi archivo lleva ese sufijo, si no cambian el modo de compilación tendrán un sunmiv2-debug.aar que servirá exactamente igual

2. Crear una librería que encapsule nuestro AAR en Visual Studio

Para este proceso utilicé Visual Studio 2019, lo primero será crear un proyecto del tipo Android Binding Library (Xamarin)

El nombre que le pondré al proyecto es el nombre que después será usado como referencia, por lo que le dejaré SunmiV2

En nuestro nuevo proyecto deberemos añadir nuestro archivo AAR, yo he optado por ponerlo dentro de la carpeta Jars

Una vez en nuestro proyecto, debemos cambiar el Build Action por LibraryProjectZip, tardará un poco en realizar este ajuste y una vez listo construimos el DLL

Al terminar nos habrá generado el DLL que ya podemos utililzar en otros proyectos

3. Implementación en un proyecto

Debido a que nosotros trabajaremos con Xamarin Forms, he creado un proyecto limpio en Forms para hacer la implementación, pero es perfectamente factible utillizar Xamarin Android, si tu proyecto no es en Xamarin Forms, puedes saltar al paso 4.

Primero tendremos que crear una interface en la solución .NET Standard usaré el nombre de IPrinter y en Xamarin Android la implementación Printer

Al XAML principal le añadí un botón para poder hacer la prueba de impresión:

<Button Clicked="Button_Clicked" Text="Test Printer" />

Con lo que ya podemos utilizar nuestra implementación, faltando solo utilizar la librería que creamos.

private void Button_Clicked(object sender, EventArgs e)
{
  var printer = DependencyService.Get<IPrinter>();
  printer.PrintImage(...);
  printer.PrintText("...");
}

En desarrollo