rfc2822's blog

  • Archive
  • RSS
  • Contact me

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/.

Update: Someone told me that for Android 4.x, the format parameter has to be added to the Intent:

intent.putExtra("format", "3gpp");

Put it right below intent.putExtra(“pdus”, new Object[] { pdu });.

    • #Android
    • #mobile
  • 1 year ago
  • Comments
  • Permalink
Share

Short URL

TwitterFacebookPinterestGoogle+

Recent comments

Blog comments powered by Disqus
← Previous • Next →

Logo

Pages

  • Author

Me, Elsewhere

  • rfc2822 on github

Following

  • staff
  • helmeloh
  • kaltspiegel
  • riverart
  • doki66
  • max-weller
  • oivoodoo
  • patrickonline
  • briilestinkt

I Dig These Posts

See more →
  • Photoset via huatunan

    色彩鹦鹉。color parrot
    Photoset via huatunan
  • Photo via androgynyous

    markrabadan:

    ASH STYMEST for OPEN LAB by MARK RABADAN.

    Photo via androgynyous
  • Photo via androgynyous
    Photo via androgynyous
  • Photo via kaltspiegel

    Bin bei meinen Eltern, hab bis Nachmittags gepennt und finde das in der Küche. Es sind die kleinen Dinge. <3

    Photo via kaltspiegel
  • Photo via riverart

    Looks like these guys turned my note 10.1 into a proper wireless wacom tablet.

    No lag, just fun. Check them out and donate if you like it :)

    Photo via riverart
  • RSS
  • Random
  • Archive
  • Contact me
  • Mobile

This content is licensed under the Creative Commons Attribute 3.0 Unported (CC BY 3.0) license.

Effector Theme by Pixel Union