androidbroadcastreceiversms

How to get sms data in a Broadcast receiver


I need to have a verified phone number for my users, but after researching, there doesn't appear to be a reliable way to get the phone# from the device. One suggestion I've seen a couple of times is to send a text message, then catch it in a Broadcast receiver and parse the phone number from the inco ming intent. I have seen this question: I could not get data from Intent after broadcast received, and this one, but they do not address my problem.

Currently, I have the receiver working, but I can't get any data about the SMS message, which presumably is accessible through the intent.

Here's the code that fires when the user presses the SEND button:

    btnSend.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                String SENT = "sent";
                String DELIVERED = "delivered";
                Intent sentIntent = new Intent(SENT);

                /*Create Pending Intents*/
                PendingIntent sentPI = PendingIntent.getBroadcast(
                        getApplicationContext(), 0, sentIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);

                /* Register for SMS send action */
                registerReceiver(new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        try {
      --->                  **SmsMessage[] smsMessage = Telephony.Sms.Intents.getMessagesFromIntent(intent);**
                            String messageBody = smsMessage[0].getMessageBody();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        int resultcode = getResultCode();
                        Log.d("  RESULT=", Integer.toString(resultcode));
                    }

                }, new IntentFilter(SENT));

                SmsManager smsManager = SmsManager.getDefault();

                if ((ContextCompat.checkSelfPermission(getApplicationContext(),
                        Manifest.permission.SEND_SMS) !=
                        PackageManager.PERMISSION_GRANTED) ||
                        (ContextCompat.checkSelfPermission(getApplicationContext(),
                                Manifest.permission.RECEIVE_SMS) !=
                                PackageManager.PERMISSION_GRANTED)){
                    ActivityCompat.requestPermissions(SMSActivity.this,
                            new String[] {
                                    Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS
                            }, 1);
                }
                smsManager.sendTextMessage(phoneNo.getText().toString(), null,
                                                msg.getText().toString(), sentPI, null);
            } catch (Exception ex) {
                Toast.makeText(getApplicationContext(),
                        ex.getMessage(), Toast.LENGTH_LONG)
                        .show();
                ex.printStackTrace();
            }
        }
    });

When the call to getMessagesFromIntent() is made (line in bold), an exception is thrown as listed here at "onReceive(SMSActivity.java:60):" The Intent is not null, so it appears that "null array" is internal to getMessagesFromIntent.

W/System.err: java.lang.NullPointerException: Attempt to get length of null array
W/System.err:     at android.provider.Telephony$Sms$Intents.getMessagesFromIntent(Telephony.java:1345)
W/System.err:     at com.jdot.jsontest.SMSActivity$1$1.onReceive(SMSActivity.java:60)
W/System.err:     at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:993)
W/System.err:     at android.os.Handler.handleCallback(Handler.java:739)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
W/System.err:     at android.os.Looper.loop(Looper.java:158)
W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:7224)
W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

I assume I may have a permissions related issue. I've added this to my manifest:

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

I've also seen this manifest addition a few times, but I don't know how to name MyBroadcastListener, since it it isn't a class like the examples I've seen.

<receiver android:name="MyBroadcastListener">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>

Finally, I tried the following block of code (as an alternative) in the the body of onReceive(). Though intent.getExtras() returns a Bundle, the call bundle.get("pdus") returns null so everything else fails.

Bundle bundle = intent.getExtras();
SmsMessage[] msgs = null;
String str = "";
if (bundle != null) {
 //---retrieve the SMS message received---
 Object[] pdus = (Object[]) bundle.get("pdus");
 msgs = new SmsMessage[pdus.length];
 for (int i=0; i<msgs.length; i++){
      msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
      str += "SMS from " + msgs[i].getOriginatingAddress();
              str += " :";
              str += msgs[i].getMessageBody().toString();
              str += "\n";
    }
//---display the new SMS message---
     Toast.makeText(context, str, Toast.LENGTH_SHORT).show();
}

My questions are:

  1. Do I need the Receiver/Intent-filter block in my manifest, and if so, how do I name the listener in the first line?
  2. What is the likely reason I can not receive a copy of the incoming message in onReceive(), and what should I do to correct it?
  3. Do I need to send a message and "catch it" in onReceive() in order to reliably get the devices phone number, or can I request permission to read SMS from the user, and just read the first message to get the incoming phone number?

Please note that I have read through all similar questions and have not found an answer.



Solution

  • Here's your problem:

    registerReceiver(..., new IntentFilter(SENT));
    

    You're registering that Receiver for the action you're using for the sent confirmation, not for the SMS_RECEIVED action, which is what is broadcast when a message arrives.

    The sent PendingIntent is used to confirm that the message has successfully left the device. The resulting Intent from that will not have any message PDUs on it, which is why you're crashing.

    The action you need to register an incoming SMS Receiver for is "android.provider.Telephony.SMS_RECEIVED", or the constant Telephony.Sms.Intents.SMS_RECEIVED_ACTION, if you're compiling with API 19 or above.


    To address the specific questions:

    1. Do I need the Receiver/Intent-filter block in my manifest, and if so, how do I name the listener in the first line?

    Registering a Receiver in the manifest will allow your app to receive messages even when it's not already running, or in the foreground. If you don't need to do that, you don't necessarily need to register in the manifest. However, if the user navigates away from your Activity before receiving the message, the paused Activity won't get the broadcast.

    The "safest" way to handle it would be to register in the manifest, but there you're registering a class, not a dynamic instance. You would need to create a MyBroadcastListener class, and somehow notify the Activity from that upon receipt; e.g., using startActivity(), or LocalBroadcastManager, or some other event bus implementation, etc.

    1. What is the likely reason I can not receive a copy of the incoming message in onReceive(), and what should I do to correct it?

    As mentioned above, the PendingIntent passed in the sendTextMessage() call is for sent confirmation. Its resulting Intent won't have the incoming message on it. You just need to listen for the correct broadcast.

    1. Do I need to send a message and "catch it" in onReceive() in order to reliably get the devices phone number, or can I request permission to read SMS from the user, and just read the first message to get the incoming phone number?

    You need to actually send a message. Incoming messages won't have the recipient number attached.