androidxmlxamarinjarunimag

UniMag card reader API won't accept xml config file during setup on Xamarin


I am implementing the UniMag credit card slider API on Xamarin Android so my app can read credit cards. The API is only for native Android so I followed the instructions here to create a wrapper around the API's .jar file. That works great and is super simple.

I can initialize the api and get listener callbacks when connecting and disconnecting the slider device. Since not all Android devices are supported by the API the UniMagReader needs to read a provided config xml file.

The problem I am having is that I cannot get the API to read the file.

The API call to use for this is;

var reader = new UniMagReader(new UniMagMessage(), Android.App.Application.Context);
reader.SetSaveLogEnable(false);

reader.SetVerboseLoggingEnable(true);
reader.RegisterListen();

string fileNameWithPath = GetConfigurationFileFromRaw();
reader.SetXMLFileNameWithPath(fileNameWithPath);
reader.LoadingConfigurationXMLFile(false);

I have tried putting the file in the Resources.Raw, Resources.Assets and the Root directories and tried a few ways of providing the URL to the API.

This is the way the demo app suggests:

  private string GetXMLFileFromRaw(string fileName)
    {
        //the target filename in the application path
        string fileNameWithPath = null;
        fileNameWithPath = fileName;

        try
        {
            //using (var inStream = Android.App.Application.Context.Assets.Open("default_config.xml")) // assest file access
            using (var inStream = Android.App.Application.Context.Resources.OpenRawResource(Resource.Raw.default_config)) // raw file access
            {
                var length = GetStreamLength(inStream);
                byte[] buffer = new byte[length];
                inStream.Read(buffer, 0, (int)length);
                inStream.Close();
                Android.App.Application.Context.DeleteFile(fileNameWithPath);
                var fout = Android.App.Application.Context.OpenFileOutput(fileNameWithPath, Android.Content.FileCreationMode.Private);
                fout.Write(buffer, 0, (int)length);
                fout.Close();

                // to refer to the application path
                var fileDir = Android.App.Application.Context.FilesDir;
                fileNameWithPath = fileDir.Parent + Path.DirectorySeparatorChar + fileDir.Name;
                fileNameWithPath += Path.DirectorySeparatorChar + "default_config.xml";
            }
        }
        catch (System.Exception e)
        {
            fileNameWithPath = null;
        }
        return fileNameWithPath;
    }

    // Return the length of a stream that does not have a usable Length property
    public static long GetStreamLength(Stream stream)
    {
        long originalPosition = 0;
        long totalBytesRead = 0;

        if (stream.CanSeek)
        {
            originalPosition = stream.Position;
            stream.Position = 0;
        }

        try
        {
            byte[] readBuffer = new byte[4096];

            int bytesRead;

            while ((bytesRead = stream.Read(readBuffer, 0, 4096)) > 0)
            {
                totalBytesRead += bytesRead;
            }
        }
        finally
        {
            if (stream.CanSeek)
            {
                stream.Position = originalPosition;
            }
        }

        return totalBytesRead;
    }

And this is another way I tried:

var file = new Java.IO.File(Android.Net.Uri.Parse("file:///default_config.xml").ToString());
var uri = file.AbsolutePath;

The errors I am getting are coming from the API. When connecting the swiper to the device I see:

 [UMSDK] SDK: headset attached
 [UMSDK] SDK: reader attached, but no config loaded

From the OnReceiveMsgFailureInfo Callback I see "The XML file does not exist and the auto update disabled."

And in the Application Output I see:

[UMSDK] UmXmlParser: parsing XML failed due to exception
[UMSDK] org.apache.harmony.xml.ExpatParser$ParseException: At line 1, column 0: not well-formed (invalid token)
[UMSDK]     at org.apache.harmony.xml.ExpatParser.parseFragment(ExpatParser.java:519)
[UMSDK]     at org.apache.harmony.xml.ExpatParser.parseDocument(ExpatParser.java:478)
[UMSDK]     at org.apache.harmony.xml.ExpatReader.parse(ExpatReader.java:316)
[UMSDK]     at org.apache.harmony.xml.ExpatReader.parse(ExpatReader.java:279)
[UMSDK]     at com.idtechproducts.acom.AcomXmlParser.parseFile(AcomXmlParser.java:91)
[UMSDK]     at com.idtechproducts.unimagsdk.UniMagConfigHelper.loadingXMLFile(UniMagConfigHelper.java:116)
[UMSDK]     at com.idtechproducts.unimagsdk.UniMagConfigHelper.loadingXMLFile(UniMagConfigHelper.java:46)
[UMSDK]     at IDTech.MSR.uniMag.uniMagReader.loadingConfigurationXMLFile(uniMagReader.java:496)

Solution

  • The sample has the file placed in the Assets or Raw folder. To use a file that is in the Assets or Raw folders, you have to get a stream and then write that stream to a file that is accessible outside of the app itself (any file added to the app's project in the IDE ends up packaged within the APK itself so is not accessible by any thing outside of the APK itself). The Java sample you sent does do just that (copied from the sample in your post, but abbreviated to only show specific lines):

    private string GetXMLFileFromRaw(string fileName)
    {
    ...
        try
        {
            // Gets the stream from the raw resource
            using (var inStream = Android.App.Application.Context.Resources.OpenRawResource(Resource.Raw.default_config)) // raw file access
            {
                // Reads the stream into a buffer
                ...
                inStream.Read(buffer, 0, (int)length);
                ...
                // Deletes any existing file
                Android.App.Application.Context.DeleteFile(fileNameWithPath);
                // Writes the stream to a file
                ...
                fout.Write(buffer, 0, (int)length);
                ...
            }
        }
        ...
    }
    

    So first set up the file name and path variables:

    string filename = "default_config.xml";
    string directory = Android.App.Application.Context.FilesDir.AbsolutePath;
    string fullPath = Path.Combine(directory, filename);
    string xmlFileContents;
    

    Then get a stream.

    If default_config.xml file in assets:

    using (StreamReader sr = new StreamReader(Assets.Open(filename)))
    

    If default_config.xml in Raw resources:

    using (StreamReader sr = new StreamReader(Resources.OpenRawResource(Resource.Raw.default_config))
    

    Then do the following:

    {
        xmlFileContents = sr.ReadToEnd();
        if (File.Exists(fullPath)) {
            File.Delete(fullPath);
        }
        File.WriteAllText(fullPath, xmlFileContents);
    }
    

    Then you can pass fullPath to the API:

    reader.SetXMLFileNameWithPath(fullPath);