Android Tutorial Intent Filters - Intercepting SMS messages in the background

Status
Not open for further replies.
Old tutorial. Don't use services. Use receivers.
Broadcast receivers are program components that can handle broadcasted messages. These messages usually notify about a system event.

There are two types of receivers in Android: statically registered receivers and dynamically registered receivers.

Static registered receivers are receivers that are declared in the manifest file.
Dynamic registered receivers are registered at runtime by calling the Java registerReceiver method.

In Basic4android you can register dynamic receivers with the BroadcastReceiver library. PhoneEvents and SmsInterceptor objects from the Phone library also use dynamic registration to listen for common intents.

Difference between static and dynamic receivers
The main difference between the two types of receivers is that dynamic receivers listen to intents as long as the process is running.

Static receivers always work. If the process is not running then it will be created.

Normal processes eventually get killed. This means that you cannot rely on dynamic receivers to intercept intents when your application is in the background. A possible workaround is to call Service.StartForeground in the service. This will prevent the process from being killed. However this will also add an ongoing notification icon (see the Services tutorial for more information).

So if you need to always listen for a specific type of intents then you may prefer to use a static receiver. Note that some intents can only be intercepted with dynamic receivers.

Static receivers
Each service module in Basic4android is made of two components. The service and a receiver. The receiver responsibility is to delegate broadcast intents to the service. For example when you call StartServiceAt, it is the receiver that actually intercepts the intent and wakes the service.

If we want to listen for intents we need to define an intent filter in the manifest file. See this link for more information about intent filters.

For example if we want to listen for intents with the action: android.provider.Telephony.SMS_RECEIVED we will need to add the following manifest editor code:
B4X:
AddPermission(android.permission.RECEIVE_SMS)
AddReceiverText(s1,
<intent-filter>
    <action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>)
s1 is the name of the service module that the intent will be delegated to.
We also add the RECEIVE_SMS permission which is required in this case.

Once this program is installed the service will be started each time that an Sms message arrives. It doesn't matter whether the process is running or not.

In our Service_Start event we check the intent action. If it fits then the messages will be parsed from the intent. This is done with the help of the Reflection library:
B4X:
'Service module
Sub Process_Globals
   Type Message (Address As String, Body As String)
End Sub
Sub Service_Create

End Sub

Sub Service_Start(startingIntent As Intent)
   If startingIntent.Action = "android.provider.Telephony.SMS_RECEIVED" Then
      Dim messages() As Message
      messages = ParseSmsIntent(startingIntent)
      For i = 0 To messages.Length - 1
         Log(messages(i))
      Next
   End If
   Service.StopAutomaticForeground
End Sub

'Parses an SMS intent and returns an array of messages
Sub ParseSmsIntent (in As Intent) As Message()
   Dim messages() As Message
   If in.HasExtra("pdus") = False Then Return messages
   Dim pdus() As Object
   Dim r As Reflector
   pdus = in.GetExtra("pdus")
   If pdus.Length > 0 Then
      Dim messages(pdus.Length) As Message
      For i = 0 To pdus.Length - 1
         r.Target = r.RunStaticMethod("android.telephony.SmsMessage", "createFromPdu", _
            Array As Object(pdus(i)), Array As String("[B"))
         messages(i).Body = r.RunMethod("getMessageBody")
         messages(i).Address = r.RunMethod("getOriginatingAddress")
      Next
   End If
   Return messages
End Sub

Update 2018: Static intent filters will cause the service to start while the app is not in the foreground. This means that on newer devices it will only work with B4A v8+ and that we need to call Service.StopAutomaticForeground once the task has completed.
https://www.b4x.com/android/forum/threads/automatic-foreground-mode.90546/#post-572424
 
Last edited:

guidoarfini

Member
Licensed User
Longtime User
I looked at the phone library:sign0085:, but I do not understand how to catch the action "send message", since I will not get this from the log, but I would like that every time they send is stored in a db, even if the user deletes the sms ...
 

lemonisdead

Well-Known Member
Licensed User
Longtime User
Hello,

May I ask you more on how to use the intents and especially how you found the correct intent provider from the Android's documentation, please ? Starting from your example, I searched for "SMS_RECEIVED" and the result points to http://developer.android.com/reference/android/telephony/package-summary.html so I understand the root of the intent "android.provider.telephony" (even if the class listed on the android's site is "android.telephony". But from that page I don't find any link to the "SMS_RECEIVED" last part of your intent. So I am a bit lost...

Thanks a lot
 

lemonisdead

Well-Known Member
Licensed User
Longtime User
Hello NJDude,

Thanks for the link. I read and tried to understand but perhaps because English is not my native language, I still do not understand where (from the Android's site) to find the intent class to put into the manifest file. Especially about Static receivers.

Taking again and again Erel's tutorial, I never can find the correct action from the API #14 : android.provider.Telephony.SMS_RECEIVED
It is probably written but I don't find it. So, if I scout the API, I find the package name 'android.telephony' but never 'SMS_RECEIVED'
 

lemonisdead

Well-Known Member
Licensed User
Longtime User
Hello Erel,

In fact, I try to understand the tutorial and especially where to find the information about the intent to use in the manifest. Refering to Android's Reference I never find the "provider.telephony" nor "SMS_RECEIVED". But they exist and your example proves it :)

Let's take another example, perhaps it could be simpler : launch an app when the phone is ringing (not to have a service always running).
I search the reference and I get "android.telephony.PhoneStateListener" pointing to "CALL_STATE_RINGING". On this last page, the class has become "android.telephony.TelephonyManager" with a public constructor "PhoneStateListener".

They say:

public static final String ACTION_PHONE_STATE_CHANGED

Broadcast intent action indicating that the call state (cellular) on the device has changed.

The EXTRA_STATE extra indicates the new call state. If the new state is RINGING, a second extra EXTRA_INCOMING_NUMBER provides the incoming phone number as a String.

Requires the READ_PHONE_STATE permission.
...
Constant Value: "android.intent.action.PHONE_STATE"

So, I guess I should put this in the manifest (with S1 the name of the service in the activity to start) ?
B4X:
AddPermission(android.permission.READ_PHONE_STATE)
AddReceiverText(s1,
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>)

and detect the RINGING state inside the service launched or by adding the EXTRA_STATE state filter with the CALL_STATE_RINGING
 
Last edited:

Rolando Bertone

New Member
Licensed User
Longtime User
Hello to all.
I am a new user and i do not understand how to retrieve the sms smessage trapped by the S1 service module.
Can someone post an example of how to place the message in an edittext and in a string variable in my main module?

Thank you.

Rolando
 

LucaMs

Expert
Licensed User
Longtime User
I have read (rather quickly) this tutorial and the description of this same subject in the Phone Library v1.75.

What I want to achieve is:
give the opportunity to the user to enable the reception of a SMS in his (my) application at his discretion, or when he is waiting for a sms suitable for the app.

That is, I do not want a service that is constantly listening (also consume the battery, right? But not only for this reason).

Which method do you recommend?

Thank you in advance
 

LucaMs

Expert
Licensed User
Longtime User
You should use the static solution that is explained in this tutorial. You can save the user settings in a file and then check the settings before you do anything with the sms.

Perfect.

Thank you, Erel
 

gawie007

Member
Licensed User
Longtime User
You will not be able to abort the broadcast with the static intent filter.
Hi Erel,
I ask in hope!: Has anyone found a way around this as yet (reflection library maybe)?

I am specifically using intents so that I am almost guaranteed to receive messages from instruments in the field.
These send their information at midnight - the end of a 24 hour day.
In order to not wake any users, I would like to consume the SMS.
I then automatically send out SMS's to various key personnel at say around 08H00 with the data received at midnight.

If not then I will have to find a way of muting the message when it is received - if that is possible.
I know SMS's, when broadcast, don't wait around too long for code to execute, so may already be producing sound while the app is trying to mute it.
 

gawie007

Member
Licensed User
Longtime User
You will not be able to abort the broadcast with the static intent filter.
Hi Erel,
I ask in hope!: Has anyone found a way around this as yet (reflection library maybe)?

I am specifically using intents so that I am almost guaranteed to receive messages from instruments in the field.
These send their information at midnight - the end of a 24 hour day.
In order to not wake any users, I would like to consume the SMS.
I then automatically send out SMS's to various key personnel at say around 08H00 with the data received at midnight.

If not then I will have to find a way of muting the message when it is received - if that is possible.
I know SMS's, when broadcast, don't wait around too long for code to execute, so may already be producing sound while the app is trying to mute it.
 

gawie007

Member
Licensed User
Longtime User
Reflection will not help. You can use a dynamic receiver and use Service.StartForeground to keep it running all the time.
Thank you Erel, but if it is dynamic and it is shut down I could miss the message. With intents, my service will be restarted if necessary every time a SMS comes in.

I found this: http://stackoverflow.com/questions/5380192/sms-receive-with-no-notification
B4X:
<receiver android:name="mypackage.SMSReceiver">
  <intent-filter>
    <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
  </intent-filter>
</receiver>

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

B4X:
public class SMSReceiver extends BroadcastReceiver
{
  @Override
  public void onReceive(Context context, Intent intent)
  {
    Bundle extras = intent.getExtras();

    Object[] pdus = (Object[])extras.get("pdus");
    for (Object pdu: pdus)
    {
      SmsMessage msg = SmsMessage.createFromPdu((byte[])pdu);

      String origin = msg.getOriginatingAddress();
      String body = msg.getMessageBody();

      // Parse the SMS body
      if (isMySpecialSMS)
      {
        // Stop it being passed to the main Messaging inbox
        abortBroadcast();
      }
    }
  }
}

This had the following comments:
While this does answer the question correctly, you should not do this. Other apps might want or need to receive the SMS_RECEIVED broadcast. Aborting it will disrupt 3rd party apps from running properly. This is a bad way to program.

The class (service?) above is started by an intent.
I know that it is my module sending me the information (Tel No. and the way the message is composed), so no other app would need the message.
I will only be aborting it if is 15 minutes either side of midnight. Any other time I want to be notified - alarms etc.
Am I missing something?

I have come up with something that works; a second Service that stops ringing and vibrating (23:50 to 00:10):
B4X:
#Region  Service Attributes
    #StartAtBoot: true
#End Region

Sub Process_Globals

    Dim muteTimer As Timer
    Dim busyCreatingService As Boolean
    Dim ringMode As Int
 
End Sub
Sub Service_Create

    busyCreatingService = True

End Sub

Sub Service_Start (StartingIntent As Intent)

    Dim HH As Int = 11
    Dim MM As Int = 50
 
    StartServiceAt(Me, NextTimeInstance(HH, MM) ,True)
 
    If busyCreatingService = False Then        'Running, NOT initialising
        Log("Muting messages")
        Dim p As Phone
        ringMode = p.GetRingerMode
        p.SetRingerMode(p.RINGER_SILENT)
        Log("Set ringermode from " & ringMode & "to " & p.GetRingerMode)
        muteTimer.Initialize("muteTimer", 1000*60*2) 'Test 2 min, 20 min when running
        muteTimer.Enabled = True
    End If
    busyCreatingService = False
 
End Sub

Sub muteTimer_Tick

    Log("UNmuting messages")
    Dim p As Phone
    muteTimer.Enabled = False
    p.SetRingerMode(ringMode)
    Log("Set ringermode back to: " & p.GetRingerMode)

End Sub

Sub NextTimeInstance (Hours As Int, Minutes As Int) As Long
  'Code courtesy of Erel:
      Dim today As Long = DateTime.Now
      today = DateUtils.SetDateAndTime(DateTime.GetYear(today), DateTime.GetMonth(today), _
        DateTime.GetDayOfMonth(today), Hours, Minutes, 0)
      If today < DateTime.Now Then
        Dim p As Period
        p.Days = 1
        Dim tomorrow As Long = DateUtils.AddPeriod(today, p)
        Return tomorrow
      Else
        Return today
      End If

End Sub

Sub Service_Destroy

End Sub
 
Last edited:

gawie007

Member
Licensed User
Longtime User
This is why I wrote that you should call Service.Startforeground. The process will not be killed.
Hi Erel, please excuse my ignorance ... I don't mean to be a pain in the butt!

From the Service Module tutorial:
Paused processes are the first to be killed when needed. If there is still not enough memory, background processes will be killed.
Foreground processes will usually not be killed.
if this read "Foreground processes will not be killed"

Sub Service_Start is called each time you call StartService (or StartServiceAt). When this subs runs the process is moved to the foreground state. Which means that the OS will not kill your process until this sub finishes running. If you want to run some code every couple of minutes / hours you should schedule the next task with StartServiceAt inside this sub.

Would this foreground state not be lost once another Activity is opened(come to the foreground) since they are on the same thread?

Understanding this will be extremely beneficial to me since I need to know that I will not miss incoming data.
 
Status
Not open for further replies.
Top