at.tuwien.mnsa.smssender.SMS.java Source code

Java tutorial

Introduction

Here is the source code for at.tuwien.mnsa.smssender.SMS.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package at.tuwien.mnsa.smssender;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.ArrayUtils;

/**
 *
 * @author Thomas
 */
public class SMS {
    private String recipient;
    private String text;

    /**
     * Create a new SMS
     * @param recipient e.g. +436604678660 (international format)
     * @param text 
     */
    public SMS(String recipient, String text) {
        //normalize receipient
        if (recipient.startsWith("+")) {
            recipient = recipient.substring(1);
        } else if (recipient.startsWith("00")) {
            recipient = recipient.substring(2);
        } else if (recipient.startsWith("0")) {
            recipient = recipient.substring(1);
        }

        this.recipient = recipient;
        this.text = text;
    }

    /**
     * @return the recipient
     */
    public String getRecipient() {
        return recipient;
    }

    /**
     * @return the text
     */
    public String getText() {
        return text;
    }

    /**
     * Get the PDU representation of a single SMS message (<= 160 chars)
     * @return 
     */
    public SMSRepresentation getMessage() {
        //https://en.wikipedia.org/wiki/GSM_03.40
        //https://stackoverflow.com/questions/12298558/how-to-build-concatenated-sms-pdu-getting-junk-chars
        //http://mobiletidings.com/2009/02/18/combining-sms-messages/

        List<Byte> bytes = new ArrayList<>();

        bytes.add((byte) 0x00); //SMSC Information
        bytes.add((byte) 0x11); //First octed of the SMS-Submit message

        bytes.add((byte) 0x00); //TP-Message-Reference (0: phone default)

        int lenRec = this.recipient.length();
        bytes.add((byte) lenRec);
        bytes.add((byte) 0x91);

        bytes.addAll(Arrays.asList(ArrayUtils.toObject(getSemiOctet(this.recipient))));

        bytes.add((byte) 0x00); //TP-PID Protocol Identifier
        bytes.add((byte) 0x00); //7 bit encoding
        bytes.add((byte) 0xAA); //validity (4 days) - @TODO: optional?

        SMSPDUConverter.SMSPDUConversionResult result = SMSPDUConverter.getInstance().getContent(this.text);

        int lenContent = result.len; //count of septets
        bytes.add((byte) lenContent);

        byte[] message = result.message;
        bytes.addAll(Arrays.asList(ArrayUtils.toObject(message)));

        String sMessage = DatatypeConverter
                .printHexBinary(ArrayUtils.toPrimitive(bytes.toArray(new Byte[bytes.size()])));
        int smsLen = (sMessage.length() / 2) - 1;

        SMSRepresentation r = new SMSRepresentation(sMessage, smsLen);

        return r;
    }

    /**
     * Get the list of single SMS for a possible concatenated SMS
     * @return 
     */
    public List<SMSRepresentation> getMultiMessage() {
        List<SMSRepresentation> messages = new LinkedList<>();

        //try to get all in a single message
        SMSRepresentation single = this.getMessage();
        if (single.len <= 160) {
            messages.add(single);
            return messages;
        }

        //else, one message is not sufficient :-(
        //we have to split the messages
        String remainingText = this.text;
        int len = 153;
        int remaining = this.text.length();

        List<SMSPDUConverter.SMSPDUConversionResult> messageResults = new ArrayList<>();
        while (remaining > 0) {
            String text = remainingText.substring(0, Math.min(remainingText.length(), len));

            SMSPDUConverter.SMSPDUConversionResult conversionResult = SMSPDUConverter.getInstance().getContent(text,
                    1);
            if (conversionResult.len > 153) {
                //we got too much :-(
                //try shorten it
                len--;
                continue;
            }
            //add the message
            messageResults.add(conversionResult);

            remaining = remaining - len;

            //continue in the text
            if (remaining > 0) {
                remainingText = remainingText.substring(len);
            }

        }

        for (int i = 0; i < messageResults.size(); i++) {
            List<Byte> bytes = new ArrayList<>();

            bytes.add((byte) 0x00); //SMSC Information
            bytes.add((byte) 0x41); //First octed of the SMS-Submit message

            bytes.add((byte) i); //TP-Message-Reference (0: phone default)
            //The message reference should be different with each SMS-SUBMIT PDU sent. (mobiletidings.com)

            int lenRec = this.recipient.length();
            bytes.add((byte) lenRec);
            bytes.add((byte) 0x91);

            bytes.addAll(Arrays.asList(ArrayUtils.toObject(getSemiOctet(this.recipient))));

            bytes.add((byte) 0x00); //TP-PID Protocol Identifier
            bytes.add((byte) 0x00); //7 bit encoding
            //bytes.add((byte) 0xAA); //validity (4 days) - @TODO: optional?

            int lenContent = Math.min(160, messageResults.get(i).len + 7); //count of septets
            bytes.add((byte) lenContent);

            bytes.add((byte) 0x05); //User data header length (UDHL)

            bytes.addAll(getMultiMessagePayloadHeader(i + 1, messageResults.size()));

            byte[] message = messageResults.get(i).message;
            bytes.addAll(Arrays.asList(ArrayUtils.toObject(message)));

            String sMessage = DatatypeConverter
                    .printHexBinary(ArrayUtils.toPrimitive(bytes.toArray(new Byte[bytes.size()])));
            int smsLen = (sMessage.length() / 2) - 1;

            messages.add(new SMSRepresentation(sMessage, smsLen));
            //System.out.println("length: " + (((messages.get(i).length()/2)-1)));
        }

        //return DatatypeConverter.printHexBinary(ArrayUtils.toPrimitive(bytes.toArray(new Byte[bytes.size()])));

        return messages;
    }

    public class SMSRepresentation {

        public final String hex;
        public final int len;

        private SMSRepresentation(String hex, int len) {
            this.hex = hex;
            this.len = len;
        }
    }

    private List<Byte> getMultiMessagePayloadHeader(int part, int total) {
        List<Byte> header = new LinkedList<>();

        header.add((byte) 0x0); //IEI -> Multimessage
        header.add((byte) 0x03); //Information element header length (3 more octets are coming!)
        header.add((byte) 0x0); //Ref nr (has to be the same - @TODO: Random?)
        header.add((byte) total); //# messages in total
        header.add((byte) part); //number of this message, starting at 1

        return header;
    }

    /**
     * Convert to Semi Octet
     * e.g. Dez: 15 -> Hex 51
     *       Dez 50 -> Hex 05
     * @param number Phone number String
     * @return 
     */
    private byte[] getSemiOctet(String number) {
        if (number.length() % 2 == 1) {
            number += "F";
        }
        byte[] ret = new byte[number.length() / 2];
        for (int i = 0; i < number.length() / 2; i++) {
            int lower = (byte) Integer.parseInt(number.substring((i * 2 + 1), i * 2 + 2), 16);
            int higher = (byte) Integer.parseInt(number.substring((i * 2), i * 2 + 1), 16);
            byte hex = (byte) ((lower << 4) | higher);

            ret[i] = hex;
        }

        return ret;
    }

    @Override
    public String toString() {
        return "+" + this.recipient + ": " + this.text;
    }
}