DanBreakingNews
DanBreakingNews

Reputation: 11

Android print to any bluetooth printer

I've made an app for android that connects to a zebra printer via bluetooth, it works fine. This is possible thanks to a library provide from Zebra.

My problem is that if i want to use another type of printer, that will force me to program again and to use another library.

Is there any way to print to any bluetooth printer? without having to program for each specific type of brand?

Upvotes: 1

Views: 6268

Answers (2)

israel
israel

Reputation: 181

Here's a github link that provides a simple android Bluetooth printing library that works on just any Bluetooth printer. It can be easily integrated into your project. I have adapted it a little so that a Bluetooth device does not have to be selected each time we want to print.

I used it to provide printing capability for an app shown in this video. There are basically three important classes.

public class PrinterCommands {
public static final byte HT = 0x9;
public static final byte LF = 0x0A;
public static final byte CR = 0x0D;
public static final byte ESC = 0x1B;
public static final byte DLE = 0x10;
public static final byte GS = 0x1D;
public static final byte FS = 0x1C;
public static final byte STX = 0x02;
public static final byte US = 0x1F;
public static final byte CAN = 0x18;
public static final byte CLR = 0x0C;
public static final byte EOT = 0x04;

public static final byte[] INIT = {27, 64};
public static byte[] FEED_LINE = {10};

public static byte[] SELECT_FONT_A = {20, 33, 0};

public static byte[] SET_BAR_CODE_HEIGHT = {29, 104, 100};
public static byte[] PRINT_BAR_CODE_1 = {29, 107, 2};
public static byte[] SEND_NULL_BYTE = {0x00};

public static byte[] SELECT_PRINT_SHEET = {0x1B, 0x63, 0x30, 0x02};
public static byte[] FEED_PAPER_AND_CUT = {0x1D, 0x56, 66, 0x00};

public static byte[] SELECT_CYRILLIC_CHARACTER_CODE_TABLE = {0x1B, 0x74, 0x11};

public static byte[] SELECT_BIT_IMAGE_MODE = {0x1B, 0x2A, 33, -128, 0};
public static byte[] SET_LINE_SPACING_24 = {0x1B, 0x33, 24};
public static byte[] SET_LINE_SPACING_30 = {0x1B, 0x33, 30};

public static byte[] TRANSMIT_DLE_PRINTER_STATUS = {0x10, 0x04, 0x01};
public static byte[] TRANSMIT_DLE_OFFLINE_PRINTER_STATUS = {0x10, 0x04, 0x02};
public static byte[] TRANSMIT_DLE_ERROR_STATUS = {0x10, 0x04, 0x03};
public static byte[] TRANSMIT_DLE_ROLL_PAPER_SENSOR_STATUS = {0x10, 0x04, 0x04};

public static final byte[] ESC_FONT_COLOR_DEFAULT = new byte[] { 0x1B, 'r',0x00 };
public static final byte[] FS_FONT_ALIGN = new byte[] { 0x1C, 0x21, 1, 0x1B,
        0x21, 1 };
public static final byte[] ESC_ALIGN_LEFT = new byte[] { 0x1b, 'a', 0x00 };
public static final byte[] ESC_ALIGN_RIGHT = new byte[] { 0x1b, 'a', 0x02 };
public static final byte[] ESC_ALIGN_CENTER = new byte[] { 0x1b, 'a', 0x01 };
public static final byte[] ESC_CANCEL_BOLD = new byte[] { 0x1B, 0x45, 0 };


/*********************************************/
public static final byte[] ESC_HORIZONTAL_CENTERS = new byte[] { 0x1B, 0x44, 20, 28, 00};
public static final byte[] ESC_CANCLE_HORIZONTAL_CENTERS = new byte[] { 0x1B, 0x44, 00 };
/*********************************************/

public static final byte[] ESC_ENTER = new byte[] { 0x1B, 0x4A, 0x40 };
public static final byte[] PRINTE_TEST = new byte[] { 0x1D, 0x28, 0x41 };

}

Another class shown below helps you select the printer from a list of paired Bluetooth devices.

public class DeviceListActivity extends Activity {
protected static final String TAG = "TAG";
private BluetoothAdapter mBluetoothAdapter;
private ArrayAdapter<String> mPairedDevicesArrayAdapter;
private static PrinterSelectedListener printerSelectedListener;

@Override
protected void onCreate(Bundle mSavedInstanceState) {
    super.onCreate(mSavedInstanceState);
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    setContentView(R.layout.devices_list);

    newConnection();
}

public static void setPrinterSelectedListener(PrinterSelectedListener printerSelectedListener_) {
    printerSelectedListener = printerSelectedListener_;
}

private void newConnection() {
    try {
        setResult(Activity.RESULT_CANCELED);
        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);

        ListView mPairedListView = findViewById(R.id.paired_devices);

        mPairedListView.setAdapter(mPairedDevicesArrayAdapter);
        mPairedListView.setOnItemClickListener(mDeviceClickListener);

        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        Set<BluetoothDevice> mPairedDevices = mBluetoothAdapter.getBondedDevices();

        if (mPairedDevices.size() > 0) {
            findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
            for (BluetoothDevice mDevice : mPairedDevices) {
                mPairedDevicesArrayAdapter.add(mDevice.getName() + "\n" + mDevice.getAddress());
            }
        } else {
            String mNoDevices = "None Paired";//getResources().getText(R.string.none_paired).toString();
            mPairedDevicesArrayAdapter.add(mNoDevices);
        }
    }catch(Exception ex){
        Log.e("exception", ex.toString());
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mBluetoothAdapter != null) {
        mBluetoothAdapter.cancelDiscovery();
    }
}

private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
    public void onItemClick(AdapterView<?> mAdapterView, View mView, int mPosition, long mLong) {

        try {


            mBluetoothAdapter.cancelDiscovery();
            String mDeviceInfo = ((TextView) mView).getText().toString();
            String mDeviceAddress = mDeviceInfo.substring(mDeviceInfo.length() - 17);
            String mDeviceName = mDeviceInfo.substring(0, mDeviceInfo.length()- 17);


            Bundle mBundle = new Bundle();
            mBundle.putString("DeviceAddress", mDeviceAddress);

            //save this printer address in sharedpreference
            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
            SharedPreferences.Editor editor = pref.edit();
            editor.putString("bluetooth_printer", mDeviceAddress); //
            editor.putString("bluetooth_name", mDeviceName);
            editor.apply();

            //respond to listerner
            if(printerSelectedListener != null)
            {
                printerSelectedListener.onPrinterSelected(mDeviceName);
            }

            Intent mBackIntent = new Intent();
            mBackIntent.putExtras(mBundle);
            setResult(Activity.RESULT_OK, mBackIntent);
            finish();
        }
        catch (Exception ex)
        {

        }
    }
};

}

The third class is the one you should extend so that the super method can print for you. This class is shown below.

public class MainActivity extends AppCompatActivity implements Runnable {
protected static final String TAG = "TAG";
protected static final int REQUEST_CONNECT_DEVICE = 1;
protected static final int REQUEST_ENABLE_BT = 2;
protected static final int BT_ON = 3;

protected BluetoothAdapter mBluetoothAdapter;
protected UUID applicationUUID = UUID
        .fromString("00001101-0000-1000-8000-00805F9B34FB");
protected ProgressDialog mBluetoothConnectProgressDialog;
protected BluetoothSocket mBluetoothSocket;
protected BluetoothDevice mBluetoothDevice;
protected OutputStream outputStream;
public String BILL = "";
protected String printerName = "";
protected boolean isChangingName = false;
protected boolean isTestingPrinter = false;

@Override
protected void onCreate(Bundle mSavedInstanceState) {
    super.onCreate(mSavedInstanceState);
    setContentView(R.layout.activity_printer);


}// onCreate

public  void doPrint(final String job) {
    String name = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("bluetooth_printer", "");
    printerName = name;
    this.BILL = job;
    this.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    if (name.equalsIgnoreCase(""))
    {

        if (this.mBluetoothAdapter == null)
        {
            Toast.makeText(getApplicationContext(), "Your Bluetooth adapter has issues", Toast.LENGTH_LONG).show();
            return;
        } else if (!this.mBluetoothAdapter.isEnabled())
        {
            //put on the bluetooth
            Intent enableBtIntent = new Intent(
                    BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent,
                    REQUEST_ENABLE_BT);
            return;
        } else
        {
            introduceNewDevice();
            return;
        }
    }else
    {
        Intent enableBtIntent = new Intent(
                BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent,
                BT_ON);
        return;
    }


}

protected void printingProcess(final String BILL, String name) {
    this.mBluetoothDevice = this.mBluetoothAdapter.getRemoteDevice(name);

    try {
        this.mBluetoothSocket = this.mBluetoothDevice.createRfcommSocketToServiceRecord(this.applicationUUID);
        this.mBluetoothSocket.connect();
    } catch (IOException eConnectException) {
        Toast.makeText(MainActivity.this, "The printer is not available. Check if it is on", Toast.LENGTH_SHORT).show();

    }

    new Thread() {
        public void run() {
            try { //outputStream
                outputStream = mBluetoothSocket.getOutputStream();


               if(isTestingPrinter){
                   //invoice details
                   printConfig(BILL, 2, 1,1);//align 1=center
                   printNewLine();
               }
                closeSocket(mBluetoothSocket); //close the connection

            } catch (Exception e) {
                Log.e("MainActivity", "Exe ", e);
            }
        }
    }.start();
}

protected void printConfig(String bill, int size, int style, int align)
{
    //size 1 = large, size 2 = medium, size 3 = small
    //style 1 = Regular, style 2 = Bold
    //align 0 = left, align 1 = center, align 2 = right

    try{

        byte[] format = new byte[]{27,33, 0};
        byte[] change = new byte[]{27,33, 0};

        outputStream.write(format);

        //different sizes, same style Regular
        if (size==1 && style==1)  //large
        {
            change[2] = (byte) (0x10); //large
            outputStream.write(change);
        }else if(size==2 && style==1) //medium
        {
            //nothing to change, uses the default settings
        }else if(size==3 && style==1) //small
        {
            change[2] = (byte) (0x3); //small
            outputStream.write(change);
        }

        //different sizes, same style Bold
        if (size==1 && style==2)  //large
        {
            change[2] = (byte) (0x10 | 0x8); //large
            outputStream.write(change);
        }else if(size==2 && style==2) //medium
        {
            change[2] = (byte) (0x8);
            outputStream.write(change);
        }else if(size==3 && style==2) //small
        {
            change[2] = (byte) (0x3 | 0x8); //small
            outputStream.write(change);
        }


        switch (align) {
            case 0:
                //left align
                outputStream.write(PrinterCommands.ESC_ALIGN_LEFT);
                break;
            case 1:
                //center align
                outputStream.write(PrinterCommands.ESC_ALIGN_CENTER);
                break;
            case 2:
                //right align
                outputStream.write(PrinterCommands.ESC_ALIGN_RIGHT);
                break;
        }
        outputStream.write(bill.getBytes());
        outputStream.write(PrinterCommands.LF);
    }catch(Exception ex){
        Log.e("error", ex.toString());
    }
}


@Override
protected void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    try {
        if (mBluetoothSocket != null)
            mBluetoothSocket.close();
    } catch (Exception e) {
        Log.e("Tag", "Exe ", e);
    }
}

@Override
public void onBackPressed() {
    try {
        if (mBluetoothSocket != null)
            mBluetoothSocket.close();
    } catch (Exception e) {
        Log.e("Tag", "Exe ", e);
    }
    setResult(RESULT_CANCELED);
    finish();
}

public void onActivityResult(int mRequestCode, int mResultCode,
                             Intent mDataIntent) {
    super.onActivityResult(mRequestCode, mResultCode, mDataIntent);

    try {
        switch (mRequestCode) {
            case REQUEST_CONNECT_DEVICE:
                if (mResultCode == Activity.RESULT_OK) {
                    Bundle mExtra = mDataIntent.getExtras();
                    String mDeviceAddress = mExtra.getString("DeviceAddress");
                    Log.e(TAG, "Coming incoming address " + mDeviceAddress);
                    mBluetoothDevice = mBluetoothAdapter
                            .getRemoteDevice(mDeviceAddress);

                    mBluetoothConnectProgressDialog = ProgressDialog.show(this,
                            "Connecting...", mBluetoothDevice.getName() + " : "
                                    + mBluetoothDevice.getAddress(), true, false);

                    mBluetoothAdapter.cancelDiscovery();
                    mHandler.sendEmptyMessage(0);

                    //don't print if we are just changing name
                    if (!isChangingName)
                        printingProcess(BILL, mDeviceAddress);
                    else {
                        Toast.makeText(MainActivity.this, "Printer selected successfully!", Toast.LENGTH_SHORT).show();

                    }
                }
                break;

            case REQUEST_ENABLE_BT:
                if (mResultCode == Activity.RESULT_OK) {
                    introduceNewDevice();
                } else {
                    Toast.makeText(MainActivity.this, "Request denied", Toast.LENGTH_SHORT).show();
                }
                break; //BT_ON
            case BT_ON:
                if (mResultCode == Activity.RESULT_OK) {
                    if (isChangingName) {
                        introduceNewDevice();
                    } else {
                        printingProcess(BILL, printerName);
                    }
                } else {
                    Toast.makeText(MainActivity.this, "Request denied", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }catch(Exception ex){
        Log.e(TAG, ex.toString());
    }
}



protected void introduceNewDevice() {
    ListPairedDevices();
    Intent connectIntent = new Intent(MainActivity.this,
            DeviceListActivity.class);
    startActivityForResult(connectIntent, REQUEST_CONNECT_DEVICE);
}

protected void ListPairedDevices() {
    try {
        Set<BluetoothDevice> mPairedDevices = mBluetoothAdapter
                .getBondedDevices();
        if (mPairedDevices.size() > 0) {
            for (BluetoothDevice mDevice : mPairedDevices) {
                Log.e(TAG, "PairedDevices: " + mDevice.getName() + "  "
                        + mDevice.getAddress());
            }
        }
    }catch(Exception ex){
        Log.e(TAG, ex.toString());
    }
}


public void run() {
    try {
        mBluetoothSocket = mBluetoothDevice
                .createRfcommSocketToServiceRecord(applicationUUID);
        mBluetoothAdapter.cancelDiscovery();
        mBluetoothSocket.connect();
        mHandler.sendEmptyMessage(0);
        Log.e("main run","inside the main run");
    } catch (IOException eConnectException) {
        Log.d(TAG, "CouldNotConnectToSocket", eConnectException);
        closeSocket(mBluetoothSocket);
        return;
    }
}

protected void closeSocket(BluetoothSocket nOpenSocket) {
    try {
        nOpenSocket.close();
        Log.d(TAG, "SocketClosed");
    } catch (IOException ex) {
        Log.d(TAG, "CouldNotCloseSocket");
    }
}

protected Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        mBluetoothConnectProgressDialog.dismiss();
       // Toast.makeText(MainActivity.this, "Device Connected", Toast.LENGTH_SHORT).show();
    }
};

public static byte intToByteArray(int value) {
    byte[] b = ByteBuffer.allocate(4).putInt(value).array();

    for (int k = 0; k < b.length; k++) {
        System.out.println("Selva  [" + k + "] = " + "0x"
                + UnicodeFormatter.byteToHex(b[k]));
    }

    return b[3];
}

public byte[] sel(int val) {
    ByteBuffer buffer = ByteBuffer.allocate(2);
    buffer.putInt(val);
    buffer.flip();
    return buffer.array();
}

//print photo
public void printPhoto(int img) {
    try {
        Bitmap bmp = BitmapFactory.decodeResource(getResources(),
                img);
        if(bmp!=null){
            byte[] command = Utils.decodeBitmap(bmp);
            outputStream.write(PrinterCommands.ESC_ALIGN_CENTER);
            printText(command);
        }else{
            Log.e("Print Photo error", "the file isn't exists");
        }
    } catch (Exception e) {
        e.printStackTrace();
        Log.e("PrintTools", "the file isn't exists");
    }
}

//print unicode
public void printUnicode(){
    try {
        outputStream.write(PrinterCommands.ESC_ALIGN_CENTER);
        printText(Utils.UNICODE_TEXT);
    } catch (UnsupportedEncodingException e) {
        Log.e("printUnicodeProblem", e.toString());
    } catch (IOException e) {
        e.printStackTrace();
    }
}


//print new line
protected void printNewLine() {
    try {
        outputStream.write(PrinterCommands.FEED_LINE);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public  void resetPrint() {
    try{
        outputStream.write(PrinterCommands.ESC_FONT_COLOR_DEFAULT);
        outputStream.write(PrinterCommands.FS_FONT_ALIGN);
        outputStream.write(PrinterCommands.ESC_ALIGN_LEFT);
        outputStream.write(PrinterCommands.ESC_CANCEL_BOLD);
        outputStream.write(PrinterCommands.LF);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//print text
protected void printText(String msg) {
    try {
        // Print normal text
        outputStream.write(msg.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }

}

//print byte[]
protected void printText(byte[] msg) {
    try {
        // Print normal text
        outputStream.write(msg);
        // printNewLine();
    } catch (IOException e) {
        Log.e("printTextError",e.toString());
    }
}


protected String leftRightAlign(String str1, String str2) {
    String ans = str1 +str2;
    if(ans.length() <31){
        int n = (31 - str1.length() + str2.length());
        ans = str1 + new String(new char[n]).replace("\0", " ") + str2;
    }
    return ans;
}

The layout activity_printer.xml in the class above should be changed to your layout. However, you would need to include this devices_list.xml file

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">
 <TextView
     android:id="@+id/title_paired_devices"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="#666"
     android:paddingLeft="5dip"
     android:text="Bluetooth Devices"
     android:textColor="#fff"
     android:visibility="gone" />

 <ListView
     android:id="@+id/paired_devices"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="#fff"
     android:layout_weight="1"
     android:stackFromBottom="true" />
</LinearLayout>

To print, simple pass your strings to the method

doPrint("your strings that needs to be printed");

I was able to make a little addition so that you can get a call back once a printer is selected. The interface is shown below. You must implement the interface in an activity where you want a user to select the printer and test it

public interface PrinterSelectedListener {

void onPrinterSelected(String name); }

To select a printer and test it in a separate activity, your activity must extend MainActivity and implement the PrinterSelectedListener. To select a new printer or change the printer, call this

isChangingName = true;
doPrint("");

To test the printer, call this

                isChangingName = false;
                isTestingPrinter = true;
                String test = "This is a test printer page" + "\n";
                doPrint(test);

Upvotes: 2

DanBreakingNews
DanBreakingNews

Reputation: 11

Hate to answer my own question, but I have made some reading about this topic

First, there is not a way to print from android to several brands of Bluetooth printers because they don’t implement some standards that make it possible.

There is a printing framework call APF that has a lot of printers register and could be useful if you find the printers models that you required for your project. But is paid and doesn’t have a good documentation. Wouldn’t use because new printers are not covered.

Best choice that you can do is to select most common type of printers, such as Zebra, Woosim, Bixolon, etc. And program for this so you have options when buying printer and don’t depend on a specific brand.

Upvotes: 0

Related Questions