Android : Simulating SMS receiving from any number

There may be times when you want to test receiving SMS on a real device without actually sending the SMSes – perhaps because you don’t own those particular sender numbers but want to check your app’s response on receiving the SMS, or because you don’t want to incur the cost (the sender numbers could be international, for instance).

This code below is not a fresh solution but re-application in 2014 of the trick described in this Japanese article written in 2010. With some minor changes, it still worked on my Samsung Galaxy S-II (Android 4.0.3).

So, here is the sample code:

Permission to be added in AndroidManifest.xml, so your app can prepare and broadcast an SMS:

<uses-permission android:name="android.permission.BROADCAST_SMS" />

The relevant code:

public class SendSmsActivity extends Activity {

    private static String TAG = "SendSmsActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        handleSmsSending();
    }

    private void handleSmsSending() {
        try {
            sendSms(this, "09000000000", "hello there!");

            Log.d(TAG, "Sent the test sms");
        } catch (Exception e) {
            Log.d(TAG, "Exception : " + e.getClass() + " : " + e.getMessage());
        }

    }

    private void sendSms(Context context, String sender, String body) throws Exception {
        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)));

        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);
        bo.write(dateBytes);

        String sReflectedClassName = "com.android.internal.telephony.GsmAlphabet";
        Class cReflectedNFCExtras = Class.forName(sReflectedClassName);
        Method stringToGsm7BitPacked = cReflectedNFCExtras.getMethod("stringToGsm7BitPacked", new Class[] { String.class });
        stringToGsm7BitPacked.setAccessible(true);
        byte[] bodybytes = (byte[]) stringToGsm7BitPacked.invoke(null, body);
        bo.write(bodybytes);
        pdu = bo.toByteArray();

        // broadcast the SMS_RECEIVED to registered receivers
        broadcastSmsReceived(context, pdu);

        // or, directly send the message into the inbox and let the usual SMS handling happen - SMS appearing in Inbox, a notification with sound, etc.
        startSmsReceiverService(context, pdu);
    }

    private void broadcastSmsReceived(Context context, byte[] pdu) {
        Intent intent = new Intent();
        intent.setAction("android.provider.Telephony.SMS_RECEIVED");
        intent.putExtra("pdus", new Object[] { pdu });
        context.sendBroadcast(intent);
    }

    private void startSmsReceiverService(Context context, byte[] pdu) {
        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 });
        intent.putExtra("format", "3gpp");
        context.startService(intent);
    }

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

Hope it helps someone with similar needs.

Advertisements

Android : Removing Raw Contacts When Using Read-Only Sync Adapters

So, for your favorite Android application, you have written a nice Read-Only Sync Adapter that allows you to create application-specific contacts in user’s address book.

Some time passes and now you need the ability to clean them up – perhaps to allow repeated rounds of testing around this “custom contacts” stuff. If you try to remove such a app-specific contact, you are prevented by Android with the following message:

Removing_Raw_Contacts

When you try to do it programmatically, you still run into the same situation, with the difference being that this time you don’t get explicitly reminded of this restriction. It’s when you start to see the unwanted side-effects, you realize that the “raw contacts” are not really being deleted – they are being hidden!

So, what do you do? You need to pass some extra information to Android – “CALLER_IS_SYNCADAPTER = true”, so that it allows your application to “really” delete the raw contact.

So, instead of the usual RawContacts uri

ContactsContract.RawContacts.CONTENT_URI

you need to use

ContactsContract.RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build()

Voila, with this little modification in the contact URI, the contact is now really deleted and not hidden!

Got caught in this trap today. Hope it helps someone avoid it!

Using C2DM? Time to migrate to Google Cloud Messaging!

OK, here is the indisputable evidence for why Google had to urgently announce “Important: C2DM has been officially deprecated as of June 26, 2012!”

Deadline Google had to meet

The Deadline Google had to meet

Somebody at Google probably remembered just in time that Michael J. Fox‘s car was being “push”ed back to the future using Android and it had better be at its best when it was time! It wouldn’t have looked good if Michael J. Fox had to return to 1985 due to a “Device Quota Exceeded” error, would it?

So, that’s the (undeniable) background – all planned by “Doc” decades back! Now, the rest of the mortal souls have to rise to the occasion, let go of the Google’s “beta” C2DM solution and migrate to the new and shiny one – Google Cloud Messaging (GCM).

But, why should we migrate?

  • For new apps that need events pushed to Android devices, there is no choice now! The Sign-up for C2DM has gone now. New applications have to use GCM.
  • Although the existing C2DM based applications will continue to work, there are not going to be quota requests accepted anymore. That’s not so future-proof, right? We don’t want to be limited to the users we currently have – we want our apps to explode! So, it makes sense for existing apps also to migrate.
  • There are many advantages too:
    • No quotas – no annoying DeviceQuotaExceeded / QuotaExceeded errors anymore. Hopefully it will remain so.
    • Availability of “push” stats – for applications published through Google Play, the GCM stats can be now be monitored – how many messages were sent each day, how many new devices registered, etc. More details on ‘how to setup your app for stats’ available here.
    • Richer API / Better Efficiency – Unlike the plain-text body that could be POSTed to the C2DM endpoint, GCM allows both plain-text / JSON formats in the POST body. The JSON format opens up new implementation opportunities like multicasting a particular message to several devices at a time, or allowing multiple senders to push to one particular device at the same time.
    • Helper libraries for client and server development. No need to deal with request/response with GCM end-point at low-level plain-text or JSON level. There are additional goodies built-in like ‘retry logic with exponential back-off.’
    • Payload size limit is 4096 bytes in GCM, compared to 1024 bytes in C2DM.

The code level changes to be done on client and server sides are well documented in the GCM documentation. So, I will just fast-forward to some specific things that I had to address:

   a) Migration approach: C2DM and GCM are not interoperable. So, an application can’t push to a C2DM-registered device through a GCM endpoint, and vice-versa. On the server-side, we need to know whether we are dealing with C2DM-registered devices or GCM-registered devices and push events through the respective endpoints. Hopefully, soon our userbase will switch over to GCM-enabled version of the Android application client, and when it reaches the point when there are no registrations marked as C2DM, we can drop the support for it and complete the migration.

   b) Eclipse specific issues:

  • Installation of the Helper libraries under Android SDK : The option to use for installing these libraries, “Extras > Google Cloud Messaging for Android Library”, does not show-up under the Android SDK Manager until you have Android SDK Tools updated to revision 20 and Android Platform SDK Tools updated to revision 12. This also necessitates the update of ADT plugin to v20.
  • NoClassDefFoundError errors post ADT plugin update : After updating the ADT plugin from v15 to v20, we started getting NoClassDefFoundError for classes coming from external libraries referenced. These jars existed under “/lib” folder. It seems the new ADT plugin looks for them under “/libs” folder. So the jars existing in “/lib” folder are not picked up for dalvik conversion by the new ADT plugin. More details are available here.

Other than these few issues, the migration has been quite an smooth exercise – helper libraries made it much easier.

Hope this information helps someone in C2DM-to-GCM migration. For more assistance, the GCM forum is here.

Cheers.

Browser detection for iOS / Android devices in a Grails application

If you want to detect the details of browser / platform your Grails application is being used from, there is this plugin called browser-detection. However, it seems like it gets it wrong sometimes. Today was one such day for me that forced me to peek closer at it.

It seems the User-Agent header values that you get from iOS and Android devices have information coming in different formats, as shown below:

  • [1] iOS – Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5
  • [2] Android – Android: Mozilla/5.0 (Linux; U; Android 2.3.3; en-gb; GT-I9100 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1

So, if you want to detect the platform, operating system, lanuguage, etc, beware of parsing the information keeping such a difference in mind, something like:

final boolean parsingForAndroid = userAgent.indexOf("Android") > 0)
// osInfo = detail string inside first parantheses, i.e., for Android - "Linux; U; Android 2.3.3; en-gb; GT-I9100 Build/GINGERBREAD"
if(parsingForAndroid) {
    (operatingSystem, sec, platform, language) = osInfo.split("; ") as List
} else {
    (platform, sec, operatingSystem, language) = osInfo.split("; ") as List
}

It’s not meant to be an extensive improvement over browser-detection plugin. The idea is just to leave a note that such differences exist when you want to detect platform details across different mobile platforms like iOS / Android.

Cheers.

References:
[1] – http://www.zytrax.com/tech/web/mobile_ids.html
[2] – http://www.gtrifonov.com/2011/04/15/google-android-user-agent-strings-2/

Adding a contact on Android device emulator

Here is something that surprised me a few times, when I tried to add contacts on the Android emulator for some tesing.

I would open the Contacts application, it would say that ([1]) there were no existing contacts, and ask me to add new ones using the menu option “New Contact”. I would enter all the details, press “Done”, and nothing would happen! It would simply come back to the previous screen and again repeat [1].

It seems that it was creating the contact alright, but just not displaying it because the relevant configuration was not done. There is another menu option next to “Next Contact”, called “Display Options”. You need to go there and enable the display of the contacts you are interested in under the category “Choose contacts to display”. Phew!