Text 11 Dec Android: generate incoming SMS from within your app

(Tested with Android 2.3.3 SDK)

Sometimes, you need to simulate an incoming short message for your app. For instance, an encryption app could need to intercept incoming SMS using a BroadcastReceiver, then check if the SMS was encrypted and if so, decrypt it. Then the decrypted SMS and not the original one should be processed further (show up in SMS app etc.).

The usual way is to create a BroadcastReceiver, intercept the SMS_RECEIVED broadcast on the incoming message and abort the broadcast if necessary (if the message was encrypted or somehow else only for your app). Then you could generate a notifcation and put the SMS into the inbox:

Putting an SMS directy into the native inbox database

You will need the WRITE_SMS privilege.

public static final String SMS_EXTRA_NAME = "pdus";
public static final String SMS_URI = "content://sms";

public static final String ADDRESS = "address";
public static final String PERSON = "person";
public static final String DATE = "date";
public static final String READ = "read";
public static final String STATUS = "status";
public static final String TYPE = "type";
public static final String BODY = "body";
public static final String SEEN = "seen";

public static final int MESSAGE_TYPE_INBOX = 1;
public static final int MESSAGE_TYPE_SENT = 2;

public static final int MESSAGE_IS_NOT_READ = 0;
public static final int MESSAGE_IS_READ = 1;

public static final int MESSAGE_IS_NOT_SEEN = 0;
public static final int MESSAGE_IS_SEEN = 1;

private void putSmsToDatabase( ContentResolver contentResolver, SmsMessage sms )
{
    ContentValues values = new ContentValues();
    values.put( ADDRESS, sms.getOriginatingAddress() );
    values.put( DATE, sms.getTimestampMillis() );
    values.put( READ, MESSAGE_IS_NOT_READ );
    values.put( STATUS, sms.getStatus() );
    values.put( TYPE, MESSAGE_TYPE_INBOX );
    values.put( SEEN, MESSAGE_IS_NOT_SEEN );
    values.put( BODY, sms.getMessageBody().toString() );

    // Push row into the SMS table
    contentResolver.insert( Uri.parse( SMS_URI ), values );
}

Source: http://stackoverflow.com/q/5946262 (modified)

However, this method is not future-safe because the content://sms URI is not specified in the SDK and may be subject to change. Also, if the user uses another SMS app than the default one, the message might not appear in the inbox of this SMS app.

Doing it by generating a SMS_RECEIVED broadcast

So I thought about the possibility to generate a SMS_RECEIVED broadcast by the app. So the message would be processed as a “real” one, including all other filters and then go into the correct SMS app, including all notifications just as from a real one.

A Google research told me that it was not possible: http://stackoverflow.com/q/3819856. I tried myself and after setting the BROADCAST_SMS permission, I didn’t get a permission error but didn’t manage to get it working. Then I found this useful posting: http://d.hatena.ne.jp/thorikawa/20100930/p1 (use a translation service if you don’t speak Japanese – I don’t) and got it working (just call sendSms from your app):

You will need the BROADCAST_SMS privilege.

     private static void sendSms(Context context, String sender, String body) {
             byte [] pdu = null ;
             byte [] scBytes = PhoneNumberUtils
                             .networkPortionToCalledPartyBCD( "0000000000" );
             byte [] senderBytes = PhoneNumberUtils
                             .networkPortionToCalledPartyBCD(sender);
             int lsmcs = scBytes.length;
             byte [] dateBytes = new byte [ 7 ];
             Calendar calendar = new GregorianCalendar();
             dateBytes[ 0 ] = reverseByte(( byte ) (calendar.get(Calendar.YEAR)));
             dateBytes[ 1 ] = reverseByte(( byte ) (calendar.get(Calendar.MONTH) + 1 ));
             dateBytes[ 2 ] = reverseByte(( byte ) (calendar.get(Calendar.DAY_OF_MONTH)));
             dateBytes[ 3 ] = reverseByte(( byte ) (calendar.get(Calendar.HOUR_OF_DAY)));
             dateBytes[ 4 ] = reverseByte(( byte ) (calendar.get(Calendar.MINUTE)));
             dateBytes[ 5 ] = reverseByte(( byte ) (calendar.get(Calendar.SECOND)));
             dateBytes[ 6 ] = reverseByte(( byte ) ((calendar.get(Calendar.ZONE_OFFSET) + calendar
                             .get(Calendar.DST_OFFSET)) / ( 60 * 1000 * 15 )));
             try {
                     ByteArrayOutputStream bo = new ByteArrayOutputStream();
                     bo.write(lsmcs);
                     bo.write(scBytes);
                     bo.write( 0x04 );
                     bo.write(( byte ) sender.length());
                     bo.write(senderBytes);
                     bo.write( 0x00 );
                     bo.write( 0x00 );  // encoding: 0 for default 7bit
                     bo.write(dateBytes);
                     try {
                             byte[] bodybytes  = GsmAlphabet.stringToGsm7BitPacked(body);
                             bo.write(bodybytes);
                     } catch(Exception e) {}

                     pdu = bo.toByteArray();
             } catch (IOException e) {
             }

             Intent intent = new Intent();
             intent.setClassName( "com.android.mms" ,
                             "com.android.mms.transaction.SmsReceiverService" );
             intent.setAction( "android.provider.Telephony.SMS_RECEIVED" );
             intent.putExtra( "pdus" , new Object[] { pdu });
             context.startService(intent);
     }

     private static byte reverseByte( byte b) {
             return ( byte ) ((b & 0xF0 ) >> 4 | (b & 0x0F ) << 4 );
     }

Source: http://d.hatena.ne.jp/thorikawa/20100930/p1 (modified)

I have used GsmAlphabet from the Android source code (I changed all EncodingExceptions to generic Exceptions for testing purposes).

For a good reference of how to create a PDU and what the single fields mean, please have a look at http://www.dreamfabric.com/sms/.


Design crafted by Prashanth Kamalakanthan. Powered by Tumblr.