Android Tutorial Wrapping "Android Library Projects"

Erel

B4X founder
Staff member
Licensed User
Longtime User
Android library projects are library projects that include resources. It is not enough to reference the compiled code (jar) as the resources are required. Copying the resources to the B4A project will not work as well as the library expects its own R.java file.

Up until v3.20 it was more or less not possible to reference such projects from Basic4android.

Now, with the new #AdditionalRes module attribute it is possible to wrap these projects.

Building a wrapper

The steps required are:
- Add the library project to Eclipse (New - Other - Android project from existing code).
Unlike other libraries, you need to have ADT installed correctly.
- Let Eclipse build the library.
- It will generate a jar file with the compiled library. The jar file will be located under the bin folder. You will need to distribute this jar together with the wrapper.
- Now you should create the wrapper library.
- You should use @DependsOn to add a reference to the compiled jar and you will also need to add a reference to the jar in Eclipse (or put the jar in the libs project if you are using SLC).

For example:

The Android library project:
SS-2014-01-05_17.11.15.png


The wrapper:
SS-2014-01-05_17.12.37.png


Using the wrapper

- Download the library project and save it somewhere.
- Copy the wrapper and the compiled jar file to the additional libraries folder.
- Add the following attribute to the project:
B4X:
#AdditionalRes: <path to the project res folder>, <library project package name>

You, the wrapper developer, should provide the package name. You can see it in the library's manifest file.
 

TheMightySwe

Active Member
Licensed User
Longtime User
Oh, I hope this is the solution to my problem with the Credit card terminal integration.

Crossing my fingers.
 

socialnetis

Active Member
Licensed User
Longtime User
What do we do with the "libs" folder from the Android library? Does the compiled jar under /bin also have the libs folder in it?
 

socialnetis

Active Member
Licensed User
Longtime User
Can we add more than one wrapper of this android libraries to a B4A project? What should we put in the
#AdditionalRes: <pathto the project res folder>, <library project package name>
line if there are more than one library project?
 

Theera

Well-Known Member
Licensed User
Longtime User
Hi all,
I've just test the SlidingMenu by using #AdditionalRes ,but I still don't understand that different from the previously stratgy (@DependOn by SLC--> Adding jar file in the lib folder) because I'm not good at English. Issue: whenever I should use #AdditionalRes or use @DependOn
Someone who has a free time to explain to me.

@Erel, Thank you for your advise about SlidingMenu,I can do it.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Most SDKs come as a single Jar file. These are simple to wrap.
However there are SDKs that include a Jar file and a resource folder (Android library projects). With these types of SDKs you cannot just copy the jar as it depends on the resource files as well. Copying the resource files will not help as it requires an additional packaging step. The AdditionalRes attribute takes care of this.
 

Theera

Well-Known Member
Licensed User
Longtime User
most SDK of them (single jar and android library project) we must create the wrapper library (jar &xml files) by using @DependOn (add reference into libs folder), but android library project must add into res folder too, Is right?

P.S. In post #1,we must only Eclipse ,or I can use notepad++ and SLC too.
 
Last edited:

TheMightySwe

Active Member
Licensed User
Longtime User
Hi, i'm really sorry but i have to ask this, i'm not good at Java at all.

This is the example code that is provided to the iSMP Companion terminal.

It is a Activity object and run as a own program. Is it possible to change the code so it becomes a library to be used with B4A?

What shall I add or change, i don't need the buttons or edittexts, I want to set the amounts from B4A, and I want the result in "displayTransactionResult" to be readable from B4A?

Does anyone have any idea how I can do this?

Regards,

/TMS


B4X:
public class MainActivity extends Activity {
    Backend backend;

    // Initiates the backend, and hooks up event handlers
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        backend = new Backend(this);

        EditText amountInput = (EditText)findViewById(R.id.input_amount);
        EditText otherAmountInput = (EditText)findViewById(R.id.input_other_amount);

        Button purchase = (Button)findViewById(R.id.btn_purchase);
        Button cashback = (Button)findViewById(R.id.btn_cashback);
        Button reversal = (Button)findViewById(R.id.btn_reversal);
        Button refund = (Button)findViewById(R.id.btn_refund);

        addAmountChangeListener(amountInput);
        addAmountChangeListener(otherAmountInput);

        purchase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onPurchase();
            }
        });

        cashback.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onCashback();
            }
        });

        reversal.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onReversal();
            }
        });

        refund.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onRefund();
            }
        });
    }

    // The backend requires de-initialization, do so
    // when the view is terminated
    @Override
    public void onDestroy() {
        super.onDestroy();
        backend.deInit();
        backend = null;
    }
   

    // The purchase button is hit.
    // Retrieve amounts from input fields and do a purchase
    // Create a toast (floating text message) notifying the
    // user of the result of the transaction
    private void onPurchase() {
        int[] amounts = getAmounts();

        backend.purchase(amounts[0], new TransactionResult() {
            @Override
            public void Result(byte[] data) {
                displayTransactionResult(data, "Purchase");
            }
        });
    }
   
    // The cashback button is hit.
    // Retrieve amounts from input fields and do a cashback
    // Create a toast (floating text message) notifying the
    // user of the result of the transaction
    private void onCashback() {
        int[] amounts = getAmounts();

        backend.cashback(amounts[0], amounts[1], new TransactionResult() {
            @Override
            public void Result(byte[] data) {
                displayTransactionResult(data, "Cashback");
            }
        });
    }

    // The reversal button is hit.
    // Perform reversal of last transaction
    // Create a toast (floating text message) notifying the
    // user of the result of the transaction
    private void onReversal() {
        backend.reversal(new TransactionResult() {
            @Override
            public void Result(byte[] data) {
                displayTransactionResult(data, "Reversal");
            }
        });
    }

    // The refund button is hit.
    // Retrieve amounts from input fields and do a refund
    // Create a toast (floating text message) notifying the
    // user of the result of the transaction
    private void onRefund() {
        int[] amounts = getAmounts();
       
        backend.refund(amounts[0], new TransactionResult() {
            @Override
            public void Result(byte[] data) {
                displayTransactionResult(data, "Refund");
            }
        });
    }

    // Decode BER-TLV encoded data, figure out if transaction was a success
    // Display trxName + [successfull transaction?] as a toast message
    private void displayTransactionResult(byte[] data, String trxName) {
        BerTlvDecoder decoder = new BerTlvDecoder(data);
        String trxStatus = "failed";
       
        if (decoder.transactionWasSuccessful()) {
            trxStatus = "succeeded";
        } else {
            trxStatus += " with error code " + decoder.transactionErrorCode();
        }
       
        String toastText = trxName + " " + trxStatus;
       
        Toast.makeText(this, toastText, Toast.LENGTH_LONG)
            .show();
    }
   
    // Change the content of an input, so that only valid amounts
    // are in the input, then move cursor to end of input.
    private void addAmountChangeListener(final EditText input) {
        input.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {}

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {}

            @Override
            public void afterTextChanged(Editable editable) {
                if (!validStringNumber(editable.toString())) {
                    input.setText(conformToValidStringNumber(editable.toString()));
                    input.setSelection(input.length());
                }
            }
        });
    }

    // Checks if a String contains a valid amount number
    private boolean validStringNumber(String number) {
        return conformToValidStringNumber(number).equals(number);
    }

    // Change a String so it contains a valid amount number
    // We rely on android options for the input field to ensure
    // that only numbers are inserted. This function basically
    // just makes sure the . is in the correct place
    private String conformToValidStringNumber(String number) {
        number = number.replace(".", "");

        if (number.length() >= 3) {
            StringBuilder tmp = new StringBuilder(number.replace(".", ""));
            tmp.insert(tmp.length() - 2, ".");
            number = tmp.toString();
        }

        return number;
    }

    // Retrieve the contents of amount inputs
    // Remove period and convert to int, then return both
    // as an int-array.
    private int[] getAmounts() {
        EditText amountInput = (EditText)findViewById(R.id.input_amount);
        EditText otherAmountInput = (EditText)findViewById(R.id.input_other_amount);

        int amount = 0;
        if (amountInput.length() > 0) {
            amount = numberFromText(amountInput.getText().toString());
        }

        int otherAmount = 0;
        if (otherAmountInput.length() > 0) {
            otherAmount = numberFromText(otherAmountInput.getText().toString());
        }

        return new int[] {
                amount,
                otherAmount
        };
    }

    // Removes period and converts to int
    private int numberFromText(String textNumber) {
        String number = textNumber.replace(".", "");
        return Integer.parseInt(number);
    }
}
 

TheMightySwe

Active Member
Licensed User
Longtime User
Do I need to add anything to this file? Does it need the B4A stuff?

And the wrapper file, is that just a .java file. Where shall I put that file? And what imports is needed in that file. I just get a bunch of errors when I write like you have done in the example.

B4X:
package com.payex.payexpos;

import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;

import com.ingenico.pclservice.PclService;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Vector;


// This class does the actual communication with
// the iSMP Companion
public class Backend {
    // See Mynt ECR Interface for _CMD and _TAG
    // values
   
    final int START_TRANSACTION_CMD = 222;
   
    final byte PURCHASE_TAG = 0;
    final byte REVERSAL_TAG = 2;
    final byte CASHBACK_TAG = 3;
    final byte REFUND_TAG = 4;

    private Context context = null;
    private PclServiceConnection pclServiceConnection = null;


    // Initialize connection
    public Backend(Context ctx) {
        context = ctx;
        pclServiceConnection = new PclServiceConnection();
        Intent intent = new Intent(ctx, PclService.class);
        ctx.bindService(intent, pclServiceConnection, Context.BIND_AUTO_CREATE);
    }

    // De-initialize connection, important to call this
    // when done with the class
    public void deInit() {
        context.unbindService(pclServiceConnection);
        context = null;
    }


    // Constructs a base BER-TLV string, containing the first
    // required field, purchase type.
    // This function should always be called for transaction
    // commands, and then you just add more BER-TLV fields on top
    // of it.
    private Vector<Byte> constructBaseTransactionWithTag(byte tag) {
        Vector<Byte> storage = new Vector<Byte>();

        storage.add((byte)-97); // Purchase type tag
        storage.add((byte)-103);
        storage.add((byte)1);
        storage.add((byte)1); // 1 in length
        storage.add(tag);

        return storage;
    }

    // Append a big endian int to a byte-vector
    private void addNumberAsBigEndian(int num, Vector<Byte> into) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.order(ByteOrder.BIG_ENDIAN);
        buffer.putInt(num);
       
        byte[] intInBytes = buffer.array();

        for (byte bite : intInBytes) {
            into.add(bite);
        }
    }

    // Helper function to add a byte-array to a byte-vector
    private void addBytesToTransaction(byte[] bytes, Vector<Byte> trx) {
        for (byte bite : bytes) {
            trx.add(bite);
        }
    }

    // Adds an BER-TLV field representing the amount of the transaction
    private void addAmountToTransaction(int amount, Vector<Byte> trx) {
        final byte[] amountTag = {
                -127, 4 // Amount tag + 4 (int32) in length
        };

        addBytesToTransaction(amountTag, trx);
        addNumberAsBigEndian(amount, trx);
    }

    // Create ECR Interface header, append BER-TLV encoded message
    // return byte-array ready to send to terminal
    private byte[] finalizeTransaction(int command, Vector<Byte> data) {
        Vector<Byte> trx = new Vector<Byte>();
       
        // Add header, as per Mynt ECR Interface
        addNumberAsBigEndian(0, trx);
        addNumberAsBigEndian(0, trx);
        addNumberAsBigEndian(command, trx);
        addNumberAsBigEndian(data.size(), trx);
       
        // Append ata
        trx.addAll(data);
       
        byte[] finalized = new byte[trx.size()];

        for (int i = 0; i < trx.size(); i++) {
            finalized[i] = trx.get(i);
        }

        return finalized;
    }

    // Send BER-TLV encoded command to the device
    // Work is performed in a background thread
    private void send(byte[] message, TransactionResult res) {
        TerminalCommunicationData data = new TerminalCommunicationData(
                message,
                pclServiceConnection.getPclService(),
                res
        );

        new DoTerminalCommunicationTask().execute(data);
    }


    // Build and send a command to the iSMP Companion
    // that starts a purchase transaction
    public void purchase(int amount, TransactionResult res) {
        Vector<Byte> trx = constructBaseTransactionWithTag(PURCHASE_TAG);
        addAmountToTransaction(amount, trx);
        byte[] command = finalizeTransaction(START_TRANSACTION_CMD, trx);
        send(command, res);
    }

    // Build and send a command to the iSMP Companion
    // that starts a cashback transaction
    public void cashback(int totalAmount, int cashbackAmount, TransactionResult res) {
        final byte[] trxCashback = {
                -97, 4, // Cashback tag
                4 // Length of (int32) cashback amount
        };

        Vector<Byte> trx = constructBaseTransactionWithTag(CASHBACK_TAG);
        addAmountToTransaction(totalAmount, trx);
        addBytesToTransaction(trxCashback, trx);
        addAmountToTransaction(cashbackAmount, trx);

        byte[] command = finalizeTransaction(START_TRANSACTION_CMD, trx);
        send(command, res);
    }

    // Build and send a command to the iSMP Companion
    // that starts a reversal transaction
    public void reversal(TransactionResult res) {
        Vector<Byte> trx = constructBaseTransactionWithTag(REVERSAL_TAG);
        byte[] command = finalizeTransaction(START_TRANSACTION_CMD, trx);
        send(command, res);
    }

    // Build and send a command to the iSMP Companion
    // that starts a refund transaction
    public void refund(int amount, TransactionResult res) {
        Vector<Byte> trx = constructBaseTransactionWithTag(REFUND_TAG);
        addAmountToTransaction(amount, trx);
        byte[] command = finalizeTransaction(START_TRANSACTION_CMD, trx);
        send(command, res);
    }

   
    // Container for data needed in a TerminalCommunication
    private class TerminalCommunicationData {
        public byte[] messageToSend;
        public PclService pclService;
        public TransactionResult transactionResult;

        public TerminalCommunicationData(byte[] mts, PclService service, TransactionResult res) {
            messageToSend = mts;
            pclService = service;
            transactionResult = res;
        }
    }
   

    // Sends command, and receives response in background thread
    // Result is passed on to given TransactionResult class
    // You might want to read on how AsyncTask works if you don't already
    // know (Android API)
    private class DoTerminalCommunicationTask
        extends AsyncTask<TerminalCommunicationData, Void, byte[]>
    {
        TerminalCommunicationData data;

        @Override
        protected byte[] doInBackground(TerminalCommunicationData... dataPackets) {
            data = dataPackets[0];

            byte[] messageToSend = data.messageToSend;
            int[] bytesSent = new int[1];
            data.pclService.sendMessage(messageToSend, bytesSent);

            byte[] messageReceived = new byte[1024];
            int[] bytesReceived = new int[1];
           
            // Keep receiving until we actually have received
            // something. There are probably better ways, but it works...
            while (bytesReceived[0] == 0) {
                data.pclService.receiveMessage(messageReceived, bytesReceived);
            }

            data.pclService.flushMessages();
           
            return messageReceived;
        }

        @Override
        protected void onPostExecute(byte[] response) {
            data.transactionResult.Result(response);
        }
    }
}
 

TheMightySwe

Active Member
Licensed User
Longtime User
Note that these questions are not really related to this thread. You can add the java source code to your library. You can use @Hide to hide it.


OK, i'll start a new thread. I really need help with this.
 

corwin42

Expert
Licensed User
Longtime User
Just a short question.

I want to wrap an android library project (lib_a) which references another android library (lib_b).

If I understand everything correctly I have to:

- Import both libraries into eclipse (lib_a and lib_b) and create them
- create a wrapper that @DependsOn lib_a and lib_b. I think the project only needs to reference lib_a
- copy all three libs to the customlibs folder (wrapper.jar, lib_a.jar, lib_b.jar)
- add two #AdditionalRes attributes (one for lib_a.jar, one for lib_b.jar)
 

sdujolo

Member
Licensed User
Longtime User
Erel can you share the wrapper eclipse project?

Thanks
 
Last edited:
Top