Click here to Skip to main content
15,887,683 members
Articles / Mobile Apps / Android

Android: ipPrint4 Print Label/Receipts to IP Printer

Rate me:
Please Sign up or sign in to vote.
4.31/5 (9 votes)
11 May 2014CPOL3 min read 18.3K   7   6
Android: ipPrint4 Print Label/Receipts to IP Printer

ipPrint4

This is an Android label/receipt printing app for TCP/IP connected printers.

This app is based on my btPrint4 app. In contrast to btPrint4, this time we print on TCP/IP connected printers.

As with btPrint4, we have a main activity and one to list available printers and one to list available demo files.

The challenge with ipPrint4 was a replacement for the bluetooth device discovery. This time, we have to scan TCP/IP address range for port 9100. This port is also called HP direct printing port and supported by many printers. It behaves similar to telnet and you can just send print commands to the interface.

The second main change to btPrint4 was the printing code. This time, we do not have to use a bluetooth socket but a network TCP/IP socket.

A TCP/IP Portscanner

If you scan a range of IP addresses in sequence and try to open a port with a timeout of, let’s say 200ms, the scan will take (200msx254, scan from 1 to 254) 50 seconds.

Port Scan Code

C#
...
ScanResult portIsOpen1(String sIp, int p, int timeout){
    ScanResult scanResult=new ScanResult(sIp, p, false);
    try {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(sIp, p), timeout);
        socket.close();
        scanResult = new ScanResult(sIp, p, true);// true;
    } catch (Exception ex) {
        doLog("Exception in portIsOpen1 for " + sIp + "/" + p);
        //return new ScanResult(ip, port, false);// false;
    }
    return scanResult;
}
...
//scanning 250 addresses with 200ms each will take 50 seconds if done one by one!
for (int ip1=1; ip1<=254; ip1++){
    String sip=String.format(baseIP + ".%03d", ip1);
    scanResult = portIsOpen1(sip, port, timeout);
    if(scanResult.isOpen){
    Log.i(TAG, scanResult.sIP + " 9100 open");
    // Send the name of the connected device back to the UI Activity
    msg = mHandler.obtainMessage(msgTypes.addHost);
    bundle = new Bundle();
    bundle.putString(msgTypes.HOST_NAME, scanResult.sIP);
    msg.setData(bundle);
    mHandler.sendMessage(msg);
    doLog("added host msg for " + scanResult.sIP);
}
else{
    Log.i(TAG, scanResult.sIP + " 9100 unavailable");
}
if(backgroundThread.interrupted())
    break;
}
...

That long time seems too much for me to let the user wait. Fortunately, there is a post at stackoverflow showing how to use an executer service to run multiple port scans in parallel.

Start Multiple Port Scans At Once

C#
...
static Future<ScanResult> portIsOpen
 (final ExecutorService es, final String ip, final int port, final int timeout) {
    return es.submit(new Callable<ScanResult>() {
        @Override public ScanResult call() {
            try {
                Socket socket = new Socket();
                socket.connect(new InetSocketAddress(ip, port), timeout);
                socket.close();
                return new ScanResult(ip, port, true);// true;
            } catch (Exception ex) {
                return new ScanResult(ip, port, false);// false;
            }
        }
    });
}
...
synchronized void startDiscovery1(){
    ...
    doLog("startDiscovery1: starting futures...");
    for (int ip1=1; ip1<=254; ip1++){
        String sip=String.format(baseIP + ".%03d", ip1);
        futures.add(portIsOpen(es, sip, port, timeout));
    }
    doLog("startDiscovery1: es.shutdown()");
    es.shutdown();
    int openPorts = 0;
    doLog("startDiscovery1 getting results...");
    for (final Future<ScanResult> f : futures) {
        try {
            if (f.get().isOpen) {
                openPorts++;
                Log.i("portScan:", f.get().sIP + " 9100 open");
                // Send the name of the connected device back to the UI Activity
                msg = mHandler.obtainMessage(msgTypes.addHost);
                bundle = new Bundle();
                bundle.putString(msgTypes.HOST_NAME, f.get().sIP);
                msg.setData(bundle);
                mHandler.sendMessage(msg);
                doLog("added host msg for " + f.get().sIP);
            }
            else{
                Log.i("portScan:", f.get().sIP + " 9100 closed");
            }
        }
        catch(ExecutionException e){
            doLog("ExecutionException: "+e.getMessage());
        }
        catch(InterruptedException e){
            doLog("InterruptedException: "+e.getMessage());
        }
    }
...
}
...

Unfortunately, the start of the Future objects is blocking and so I had to wrap that in another thread, so the caller (the GUI) is not blocked.

Thread to Start Port Scans

C#
...
@Override
public void run() {
    doLog("thread: run()...");
    if(state!=eState.idle) {
        doLog("thread: run() ended as state!=idle");
        return;
    }
    state=eState.running;
    ScanResult scanResult;
    try {
        doLog("thread: starting...");
        msg=mHandler.obtainMessage(msgTypes.started);
        mHandler.sendMessage(msg);
        if( true) {
            doLog("thread: starting discovery...");
            startDiscovery1();
        ...
   ...
...

With the above solution, the port scan finishes with 5 to 10 seconds. Every time a new open port is found, the IP is added to the list. The process shows a progress cycle in the title of the list view activity. All status changes are sent via a message handler from the portscanner code to the calling list activity.

When a TCP/IP address with port 9100 is found and selected, you can print a demo file to it. You can also enter an IP manually and then directly print a demo file to it.

Startup Checks

On startup, the app checks for TCP/IP connectivity and ends, if there is no newtwork connection. It makes no sense to run the app without a working connection.

Check Online Status

C#
...
public static boolean isNetworkOnline(Context context) {
    boolean status=false;
    try{
        ConnectivityManager cm = (ConnectivityManager)
                                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getNetworkInfo(0);
        if (netInfo != null && netInfo.getState()==NetworkInfo.State.CONNECTED) {
            status= true;
        }else {
            netInfo = cm.getNetworkInfo(1);
            if(netInfo!=null && netInfo.getState()==NetworkInfo.State.CONNECTED)
                status= true;
        }
    }catch(Exception e){
        e.printStackTrace();
        return false;
    }
    return status;
}
...

The local IP is automatically inserted as ‘printer’ IP address (remote device). So you just have to change part of the IP for the printer’s address.

IP Address Range

The portscanner code is initialized with the device IP and then uses a class C conversion to define start and end address. This will not work if you have a class B or class A network. So, there are possible improvements of the code.

Class c Network

C#
...
//constructor
PortScanner(Handler handler, String startIP){
    mHandler=handler;
    m_sStartIP=startIP;
    //test(startIP);
    String[] ss = m_sStartIP.split("\\.");
    if(ss.length!=4){ //no regular IP
        state=eState.finished;
        msg = mHandler.obtainMessage(hgo.ipprint4.msgTypes.MESSAGE_TOAST);
        bundle = new Bundle();
        bundle.putString(hgo.ipprint4.msgTypes.TOAST, "inavlid IP");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
        return;
    }
    bValidIP=true;
    baseIP=ss[0]+"."+ss[1]+"."+ss[2];
    state=eState.idle;
}
...

As we want to print to a socket, we needed to change the bluetooth connection code to connect to a network socket.

C#
...
/**
* This thread runs while attempting to make an outgoing connection
* with a device. It runs straight through; the connection either
* succeeds or fails.
*/
private class ConnectThread extends Thread {
    private Socket mmSocket=null;
    private InetAddress serverIP;
    private SocketAddress socketAddress=null;

    //private final BluetoothDevice mmDevice;

    @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
    public ConnectThread(String sIPremote) {
        _IPaddr=sIPremote;
        try {
            addText("get host IP");
            serverIP=InetAddress.getByName(_IPaddr);
            socketAddress=new InetSocketAddress(serverIP, socketPort);
            //tmp = device.createRfcommSocketToServiceRecord(UUID_SPP);
        }catch (UnknownHostException e){
            Log.e(TAG, "ConnectThread create() failed", e);
        }
        catch (IOException e) {
            Log.e(TAG, "ConnectThread create() failed", e);
        }
    }
    @Override
    public void run() {
        Log.i(TAG, "ConnectThread::run()");
        setName("ConnectThread");
        Socket tmp = null;

        // Make a connection to the Socket
        try {

            addText("new Socket()...");
            // This is a blocking call and will only return on a
            // successful connection or an exception
            //tmp=new Socket(serverIP, socketPort);
            tmp=new Socket();
            tmp.connect(socketAddress, iTimeOut);
            addText("new socket() done");
            mmSocket=tmp;
        }
        catch(IllegalArgumentException e){
            addText("IllegalArgumentException: " + e.getMessage());
            //if new Socket() failed
            connectionFailed();
            addText("Connect failed");
            if(mmSocket!=null) {
                // Close the socket
                try {
                    mmSocket.close();
                    tmp = null;
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
            }
            return;
        }
        catch (IOException e){
            addText("IOException: " + e.getMessage());
            //if new Socket() failed
            connectionFailed();
            addText("Connect failed");
            if(mmSocket!=null) {
                // Close the socket
                try {
                    mmSocket.close();
                    tmp = null;
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
            }
            // Start the service over to restart listening mode
            return;
        }
        catch (Exception e) {
            //if new Socket() failed
            connectionFailed();
            addText("Connect failed");
            if(mmSocket!=null) {
                // Close the socket
                try {
                    mmSocket.close();
                    tmp = null;
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
            }
            return;
        }//catch
...

Then, we need a stream to write and one to listen. Same as in btPrint4.

C#
...
private class ConnectedThread extends Thread {
    private final Socket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(Socket socket) {
        Log.d(TAG, "create ConnectedThread");
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        // Get the BluetoothSocket input and output streams
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            Log.e(TAG, "temp sockets not created", e);
        }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }
...

And then, we can write to the socket.

C#
...
    public void write(byte[] buffer) {
        addText("write...");
        try {
            mmOutStream.write(buffer);

            // Share the sent message back to the UI Activity
            mHandler.obtainMessage(msgTypes.MESSAGE_WRITE, -1, -1, buffer)
                    .sendToTarget();
        } catch (IOException e) {
            Log.e(TAG, "Exception during write", e);
        }
        addText("write done");
    }
...

The remaining code of ipPrintFile is similar to the one in btPrint4 and used to manage all the threads used.

Full source code can be downloaded from github.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTwo way communication with printer Pin
Mohammad Akhtar Jadhakhan13-Jan-19 8:18
Mohammad Akhtar Jadhakhan13-Jan-19 8:18 
QuestionTrouble in connecting multiple deveice at same time Pin
Member 1362833817-Jan-18 4:51
Member 1362833817-Jan-18 4:51 
GeneralMy vote of 5 Pin
Ahmed Bensaid19-Jun-14 3:47
professionalAhmed Bensaid19-Jun-14 3:47 
GeneralMy vote of 4 Pin
Sunasara Imdadhusen28-May-14 3:17
professionalSunasara Imdadhusen28-May-14 3:17 
QuestionTesting for a network connection Pin
Darren_vms14-May-14 12:06
Darren_vms14-May-14 12:06 
AnswerRe: Testing for a network connection Pin
hjgode16-May-14 0:35
hjgode16-May-14 0:35 
I adjusted my code to this:

public static boolean isNetworkOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

if (cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isAvailable()
&& cm.getActiveNetworkInfo().isConnected()) {
// so far we have a network and it's available
// we now need to see if it's wifi or mobile
NetworkInfo[] netInfo = cm.getAllNetworkInfo();
for (NetworkInfo ni : netInfo) {
if (ni.getTypeName().equalsIgnoreCase("WIFI") && ni.isConnected())
return true; // WIFI that's ok
if (ni.getTypeName().equalsIgnoreCase("MOBILE") && ni.isConnected())
{
//ask user about WWAN usage
boolean bUseWWAN=false;
bUseWWAN = myDialog(context);
Log.d( TAG, "Mobile connection found");
Toast.makeText(context, "Using WAN connection", Toast.LENGTH_LONG); //getString(R.string.Warning)); // ok so warn about charges
return true;
}
}
//Toast.makeText(context, "No known network connection found", Toast.LENGTH_LONG); //getString(R.string.Warning)); // ok so warn about charges
Log.i( TAG, "no Connection found");
return false;
} else {
android.util.Log.d( TAG, "Internet Connection Not Present");
return false;
}
/*
boolean status=false;
try{
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getNetworkInfo(0);
if (netInfo != null && netInfo.getState()==NetworkInfo.State.CONNECTED) {
status= true;
}else {
netInfo = cm.getNetworkInfo(1);
if(netInfo!=null && netInfo.getState()==NetworkInfo.State.CONNECTED)
status= true;
}
}catch(Exception e){
e.printStackTrace();
return false;
}
return status;
*/
}

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.