2: SMS Messages

Contents:

Android devices can send and receive messages to or from any other phone that supports Short Message Service (SMS). Android offers the Messenger app that can send and receive SMS messages. A host of third-party apps for sending and receiving SMS messages are also available in Google Play.

This chapter describes how to use SMS in your app. You can add code to your app to:

  • Launch an SMS messaging app from your app to handle all SMS communication.
  • Send an SMS message from within your app.
  • Receive SMS messages in your app.
Note: The SMS protocol was primarily designed for user-to-user communication and is not well-suited for apps that want to transfer data. You should not use SMS to send data messages from a web server to your app on a user device. SMS is neither encrypted nor strongly authenticated on either the network or the device.

Sending and receiving SMS messages

Access to the SMS features of an Android device is protected by user permissions. Just as your app needs the user's permission to use phone features, so also does an app need the user's permission to directly use SMS features.

However, your app doesn't need permission to pass a phone number to an installed SMS app, such as Messenger, for sending the message. The Messenger app itself is governed by user permission.

You have two choices for sending SMS messages:

  • Use an implicit Intent to launch a messaging app such as Messenger, with the ACTION_SENDTO action.
    • This is the simplest choice for sending messages. The user can add a picture or other attachment in the messaging app, if the messaging app supports adding attachments.
    • Your app doesn't need code to request permission from the user.
    • If the user has multiple SMS messaging apps installed on the Android phone, the App chooser will appear with a list of these apps, and the user can choose which one to use. (Android smartphones will have at least one, such as Messenger.)
    • The user can change the message in the messaging app before sending it.
    • The user navigates back to your app using the Back button.
  • Send the SMS message using the sendTextMessage() method or other methods of the SmsManager class.
    • This is a good choice for sending messages from your app without having to use another installed app.
    • Your code must ask the user for permission before sending the message if the user hasn't already granted permission.
    • The user stays in your app during and after sending the message.
    • You can manage SMS operations such as dividing a message into fragments, sending a multipart message, get carrier-dependent configuration values, and so on.

To receive SMS messages, the best practice is to use the onReceive() method of the BroadcastReceiver class. The Android framework sends out system broadcasts of events such as receiving an SMS message, containing intents that are meant to be received using a BroadcastReceiver. Your app receives SMS messages by listening for the SMS_RECEIVED_ACTION broadcast.

Most smartphones and mobile phones support what is known as "PDU mode" for sending and receiving SMS. PDU (protocol data unit) contains not only the SMS message, but also metadata about the SMS message, such as text encoding, the sender, SMS service center address, and much more. To access this metadata, SMS apps almost always use PDUs to encode the contents of a SMS message. The sendTextMessage() and sendMultimediaMessage() methods of the SmsManager class encode the contents for you. When receiving a PDU, you can create an SmsMessage object from the raw PDU using createFromPdu().

Using an intent to launch an SMS app

To use an Intent to launch an SMS app, your app needs to prepare a Uniform Resource Identifier (URI) for the phone number as a string prefixed by "smsto:" (as in smsto:14155551212). You can use a hardcoded phone number, such as the phone number of a support message center, or provide an EditText field in the layout to enable the user to enter a phone number.

Tip: For details about using methods in the PhoneNumberUtils class to format a phone number string, see the related concept Phone Calls.

Use a button (such as an ImageButton) that the user can tap to pass the phone number to the SMS app. For example, an app that enables a user make a phone call and/or send a message to the phone number might offer a simple layout with a phone icon button for calling, and a messaging icon button for sending a message, as shown in the figure below. The phone icon is a button for making a call

To call a method such as smsSendMessage() that would launch a messaging app with a phone number, you can add the android:onClick attribute to the button for sending a message:

<ImageButton
    ...
    android:onClick="smsSendMessage"/>

In the smsSendMessage() method, you would convert the phone number to a string prefixed by "smsto:" (as in smsto:14155551212). Use an implicit intent with ACTION_SENDTO to pass the phone number to the SMS app, and set the phone number and message for the intent with setData() and putExtra.

Tip: The "smsto:" prefix with ACTION_SENDTO ensures that your intent is handled only by a text messaging app (and not by other email or social apps).

If the user has several SMS messaging apps, the user can choose which one to open. The SMS app opens with the supplied phone number and message, enabling the user to tap a button to send the message, or change the number and message before sending. The SMS app then sends the message.

The following code demonstrates how to perform an implicit intent to send a message:

public void smsSendMessage(View view) {
    // Find the TextView number_to_call and assign it to textView.
    TextView textView = (TextView) findViewById(R.id.number_to_call);
    // Concatenate "smsto:" with phone number to create smsNumber.
    String smsNumber = "smsto:" + textView.getText().toString();
    // Find the sms_message view.
    EditText smsEditText = (EditText) findViewById(R.id.sms_message);
    // Get the text of the sms message.
    String sms = smsEditText.getText().toString();
    // Create the intent.
    Intent smsIntent = new Intent(Intent.ACTION_SENDTO);
    // Set the data for the intent as the phone number.
    smsIntent.setData(Uri.parse(smsNumber));
    // Add the message (sms) with the key ("sms_body").
    smsIntent.putExtra("sms_body", sms);
    // If package resolves (target app installed), send intent.
    if (smsIntent.resolveActivity(getPackageManager()) != null) {
        startActivity(smsIntent);
    } else {
        Log.e(TAG, "Can't resolve app for ACTION_SENDTO Intent.");
    }
}

Note the following in the above code:

  • The method gets the phone number from the number_to_call TextView, and concatenates it with the smsto: prefix (as in smsto:14155551212) before assigning it to smsNumber. It also gets the message entered into the EditText view.
  • To launch an SMS messaging app, use an implicit intent (smsIntent) with ACTION_SENDTO, and set the phone number and message for the intent with setData() and putExtra.

    The putExtra() method needs two strings: the key identifying the type of data ("sms_body") and the data itself, which is the text of the message (sms). For more information about common intents and the putExtra() method, see Common Intents: Text Messaging.

  • You need to supply a check to see if the implicit intent resolves to a package (an app), and if it does, you need to start the smsIntent activity. If it doesn't, display a log message about the failure.

Sending SMS messages from your app

To send an SMS message from your app, use the sendTextMessage() method of the SmsManager class. Perform these steps to enable sending messages from within your app:

  1. Add the SEND_SMS permission to send SMS messages.
  2. Check to see if the user continues to grant permission. If not, request permission.
  3. Use the sendTextMessage() method of the SmsManager class.

Checking for user permission

Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. This approach streamlines the app install process, since the user does not need to grant permissions when they install or update the app. It also gives the user more control over the app's functionality. However, your app must check for permission every time it does something that requires permission (such as sending an SMS message). If the user has used the Settings app to turn off SMS permissions for the app, your app can display a dialog to request permission.

Tip: For a complete description of the request permission process, see Requesting Permissions at Run Time.

Add the SEND_SMS permission to the AndroidManifest.xml file after the first line (with the package definition) and before the <application> section:

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

Because the user can turn permissions on or off for each app, your app must check whether it still has permission every time it does something that requires permission (such as sending an SMS message). If the user has turned SMS permission off for the app, your app can display a dialog to request permission.

Follow these steps:

  1. At the top of the activity that sends an SMS message, and below the activity's class definition, define a constant variable to hold the request code, and set it to an integer:

    private static final int MY_PERMISSIONS_REQUEST_SEND_SMS = 1;
    

    Why the integer 1? Each permission request needs three parameters: the context, a string array of permissions, and an integer requestCode. The requestCode is the integer attached to the request. When a result returns in the activity, it contains this code and uses it to differentiate multiple permission results from each other.

  2. In the activity that makes a phone call, create a method that uses the checkSelfPermission() method to determine whether your app has been granted the permission:

    private void checkForSmsPermission() {
       if (ActivityCompat.checkSelfPermission(this,
                        Manifest.permission.SEND_SMS) !=
                        PackageManager.PERMISSION_GRANTED) {
           Log.d(TAG, getString(R.string.permission_not_granted));
           // Permission not yet granted. Use requestPermissions().
           // MY_PERMISSIONS_REQUEST_SEND_SMS is an
           // app-defined int constant. The callback method gets the
           // result of the request.
           ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.SEND_SMS},
                        MY_PERMISSIONS_REQUEST_SEND_SMS);
       } else {
           // Permission already granted. Enable the message button.
           enableSmsButton();
       }
    }
    

    The code uses checkSelfPermission() to determine whether your app has been granted a particular permission by the user. If permission has not been granted, the code uses the requestPermissions() method to display a standard dialog for the user to grant permission.

Use your checkForSmsPermission() method to check for permission at the following times:

  • When the activity starts—in its onCreate() method.
  • Every time before sending a message. Since the user might turn off the SMS permission while the app is still running, call the checkForSmsPermission() method in the smsSendMessage() method before using the SmsManager class.

Requesting user permission

If permission has not been granted by the user, use the requestPermissions() method of the ActivityCompat class. The requestPermissions() method needs three parameters: the context (this), a string array of permissions (new String[]{Manifest.permission.SEND_SMS}), and the predefined integer MY_PERMISSIONS_REQUEST_SEND_SMS for the requestCode.

When your app calls requestPermissions(), the system shows a standard dialog to the user, as shown in the figure below. Requesting permission to send and view SMS messages

When the user responds to the request permission dialog by tapping Deny or Allow, the system invokes the onRequestPermissionsResult() method, passing it the user response. Your app has to override that method to find out whether the permission was granted.

The following code demonstrates how you can use a switch statement in your implementation of onRequestPermissionsResult() based on the value of requestCode. The user's response to the request dialog is returned in the permissions array (index 0 if only one permission is requested in the dialog). This is compared to the corresponding grant result, which is either PERMISSION_GRANTED or PERMISSION_DENIED.

If the user denies a permission request, your app should disable the functionality that depends on this permission and show a dialog explaining why it could not perform it. The code below logs a debug message, displays a toast to show that permission was not granted, and disables the message icon used as a button.

@Override
public void onRequestPermissionsResult(int requestCode,
                    String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_SEND_SMS: {
            if (permissions[0].equalsIgnoreCase(Manifest.permission.SEND_SMS)
                        && grantResults[0] ==
                        PackageManager.PERMISSION_GRANTED) {
                // Permission was granted.
            } else {
                // Permission denied.
                Log.d(TAG, getString(R.string.failure_permission));
                Toast.makeText(MainActivity.this,
                            getString(R.string.failure_permission),
                                  Toast.LENGTH_SHORT).show();
                // Disable the message button.
                disableSmsButton();
            }
        }
    }
}

Using SmsManager to send the message

Use the sendTextMessage() method of the SmsManager class to send the message, which takes the following parameters:

  • destinationAddress: The string for the phone number to receive the message.
  • scAddress: A string for the service center address, or null to use the current default SMSC. A Short Message Service Center (SMSC) is a network element in the mobile telephone network. The mobile network operator usually presets the correct service center number in the default profile of settings stored in the device's SIM card.

    Tip: You can find the default SMSC in an Android smartphone's hidden menu. Open the Phone app and dial ##4636## to open the testing menu. Tap Phone information, and scroll to the bottom. The SMSC number should appear blank. Tap Refresh to see the number.

  • smsMessage: A string for the body of the message to send.
  • sentIntent: A PendingIntent. If not null, this is broadcast when the message is successfully sent or if the message failed.
  • deliveryIntent: A PendingIntent. If not null, this is broadcast when the message is delivered to the recipient.

Follow these steps to use the sendTextMessage() method:

  1. Create an onClick handler for a button that sends the message.
  2. Get the strings for the phone number (destinationAddress) and the message (smsMessage).
  3. Declare additional string and PendingIntent parameters:
    ...
    // Set the service center address if needed, otherwise null.
    String scAddress = null;
    // Set pending intents to broadcast
    // when message sent and when delivered, or set to null.
    PendingIntent sentIntent = null, deliveryIntent = null;
    ...
    
  4. Use the SmsManager class to create smsManager, which automatically imports android.telephony.SmsManager, and use sendTextMessage() to send the message:
    ...
    // Use SmsManager.
    SmsManager smsManager = SmsManager.getDefault();
    smsManager.sendTextMessage
           (destinationAddress, scAddress, smsMessage,
                           sentIntent, deliveryIntent);
    ...
    

The following code snippet shows a sample onClick handler for sending a message:

public void smsSendMessage(View view) {
    EditText editText = (EditText) findViewById(R.id.editText_main);
    // Set the destination phone number to the string in editText.
    String destinationAddress = editText.getText().toString();
    // Find the sms_message view.
    EditText smsEditText = (EditText) findViewById(R.id.sms_message);
    // Get the text of the SMS message.
    String smsMessage = smsEditText.getText().toString();
    // Set the service center address if needed, otherwise null.
    String scAddress = null;
    // Set pending intents to broadcast
    // when message sent and when delivered, or set to null.
    PendingIntent sentIntent = null, deliveryIntent = null;
    // Check for permission first.
    checkForSmsPermission();
    // Use SmsManager.
    SmsManager smsManager = SmsManager.getDefault();
    smsManager.sendTextMessage
                  (destinationAddress, scAddress, smsMessage,
                                 sentIntent, deliveryIntent);
}

Receiving SMS messages

To receive SMS messages, use the onReceive() method of the BroadcastReceiver class. The Android framework sends out system broadcasts of events such as SMS_RECEIVED for receiving an SMS message. You must also include RECEIVE_SMS permission in your project's AndroidManifest.xml file:

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

To use a broadcast receiver:

  1. Add the broadcast receiver by choosing File > New > Other > Broadcast Receiver. The <receiver...</receiver> tags are automatically added to the AndroidManifest.xml file.
  2. Register the receiver by adding an intent filter within the <receiver...</receiver> tags to specify the type of broadcast intent you want to receive.
  3. Implement the onReceive() method.

Adding a broadcast receiver

You can perform the first step by selecting the package name in the Project:Android: view and choosing File > New > Other > Broadcast Receiver. Make sure "Exported" and "Enabled" are checked. The "Exported" option allows your app to respond to outside broadcasts, while "Enabled" allows it to be instantiated by the system.

Android Studio automatically generates a <receiver> tag in the app's AndroidManifest.xml file, with your chosen options as attributes:

<receiver
    android:name="com.example.android.phonecallingsms.MySmsReceiver"
    android:enabled="true"
    android:exported="true">
</receiver>

Registering the broadcast receiver

In order to receive any broadcasts, you must register for specific broadcast intents. In the Intent documentation, under "Standard Broadcast Actions", you can find some of the common broadcast intents sent by the system.

The following intent filter registers the receiver for the android.provider.Telephony.SMS_RECEIVED intent:

<receiver
    android:name="com.example.android.smsmessaging.MySmsReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
         <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>

Implementing the onReceive() method

Once your app's BroadcastReceiver intercepts a broadcast it is registered for (SMS_RECEIVED), the intent is delivered to the receiver's onReceive() method, along with the context in which the receiver is running.

The following shows the first part of the onReceive() method, which does the following:

  • Retrieves the extras (the SMS message) from the intent.
  • Stores it in a bundle.
  • Defines the msgs array and strMessage string.
  • Gets the format for the message from the bundle in order to use it with createFromPdu() to create the SmsMessage.

The format is the message's mobile telephony system format passed in an SMS_RECEIVED_ACTION broadcast. It is usually "3gpp" for GSM/UMTS/LTE messages in the 3GPP format, or "3gpp2" for CDMA/LTE messages in 3GPP2 format.

@Override
public void onReceive(Context context, Intent intent) {
    // Get the SMS message.
    Bundle bundle = intent.getExtras();
    SmsMessage[] msgs;
    String strMessage = "";
    String format = bundle.getString("format");
    // Retrieve the SMS message received.
    ...
}

The onReceive() method then retrieves from the bundle one or more pieces of data in the PDU:

...
// Retrieve the SMS message received.
Object[] pdus = (Object[]) bundle.get("pdus");
if (pdus != null) {
    // Fill the msgs array.
    msgs = new SmsMessage[pdus.length];
    for (int i = 0; i < msgs.length; i++) {
        ...

Use createFromPdu(byte[] pdu, String format) to fill the msgs array for Android version 6.0 (Marshmallow) and newer versions. For earlier versions of Android, use the deprecated signature createFromPdu(byte[] pdu):

  • For Android version 6.0 (Marshmallow) and newer versions, use the following, which includes format:
      msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i], format);
    
  • For earlier versions of Android, use the following:
      msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
    

The onReceive() method then builds the strMessage to show in a toast message. It gets the originating address using the getOriginatingAddress() method, and the message body using the getMessageBody() method.

...
// Build the message to show.
strMessage += "SMS from " + msgs[i].getOriginatingAddress();
strMessage += " :" + msgs[i].getMessageBody() + "\n";
...

The following shows the complete onReceive() method for SMS messages:

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onReceive(Context context, Intent intent) {
    // Get the SMS message.
    Bundle bundle = intent.getExtras();
    SmsMessage[] msgs;
    String strMessage = "";
    String format = bundle.getString("format");
    // Retrieve the SMS message received.
    Object[] pdus = (Object[]) bundle.get("pdus");
    if (pdus != null) {
        // Check the Android version.
        boolean isVersionM =
                        (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
        // Fill the msgs array.
        msgs = new SmsMessage[pdus.length];
        for (int i = 0; i < msgs.length; i++) {
            // Check Android version and use appropriate createFromPdu.
            if (isVersionM) {
                // If Android version M or newer:
                msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i], format);
            } else {
                // If Android version L or older:
                msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
            }
            // Build the message to show.
            strMessage += "SMS from " + msgs[i].getOriginatingAddress();
            strMessage += " :" + msgs[i].getMessageBody() + "\n";
            // Log and display the SMS message.
            Log.d(TAG, "onReceive: " + strMessage);
            Toast.makeText(context, strMessage, Toast.LENGTH_LONG).show();
        }
    }
}

Tip: To build an SMS app with more features, see Telephony in the Android Developer's Documentation, and read the blog post Getting Your SMS Apps Ready for KitKat.

Receiving test messages in the emulator

You can emulate receiving a call or an SMS text message by clicking the (More) icon at the bottom of the emulator's toolbar on the right side, as shown in the figure below. Click the … (More) icon for more emulator controls

The extended controls for the emulator appear. Click Phone in the left column to see the extended phone controls: Click Phone for the extra phone controls

Enter a message (or use the default "marshmallows" message) and click Send Message to send an SMS message to your emulator.

The emulator essentially sends itself the message, and responds as if receiving a call or receiving an SMS message.

Learn more

results matching ""

    No results matching ""