Monday, 5 October 2015

Managing AsyncTasks on configuration changes

The problem

Suppose you have an AsyncTask performing some task in the background (downloading data from a web service, updating an online database, etc.). If the user rotates the device, as you probably know, the current Activity is destroyed and recreated (the callback methods onDestroy and onCreate are invoked).

As a result, when the AsyncTask finishes its task and should deliver its results, the original Activity may no longer exist or it could have been replaced by a brand new Activity. However the AsyncTask is tied to the original Activity and its onPostExecute method won’t work with the newly created Activity. The newly created Activity may even start another AsyncTask (like in our example, see below), thus wasting system resources.

That’s why, as a general rule, you should always perform network operations in a Service: the Service’s lifecycle isn’t directly tied to the Activity lifecycle; a Service is not destroyed and recreated on configuration changes, like AsyncTasks do.
However AsyncTasks are generally easier and quicker to program than Services, so let’s examine some possibile solutions to our “configuration changes problem” using AsyncTasks.

Code

You can download the code for this tutorial from the Android – The Techinal Blog Githubrepository.

The easy solution

A very easy solution to our problem is preventing the Activity from being destroyed and recreated on configuration changes. For example, if you use the same layout for portrait and landscape orientation there is no need to destroy and recreate the Activity when the user rotates the device.

To achieve this, just insert the following line in the AndroidManifest.xml file under the Activity tag:
android:configChanges="orientation|screenSize|keyboardHidden”
This way, when the user rotates the device, the Activity is not destroyed and recreated anymore; conversely the method onConfigurationChanged gets called and you can override it if you need it.

The correct solution

The correct solution to the “configuration change problem” is using a so called “retained Fragment”.
A Fragment represents a behavior or a portion of the user interface within an Activity. Fragment’s life cycle is generally coordinated with the lifecycle of its containing Activity: when the Activity is destroyed on configuration changes all the contained Fragments get destroyed too.

However if you call setRetainInstance(true) within the Fragment class the destroy-and-recreated cycle of the Fragment is bypassed. As a result you can use a “retained Fragment” to hold active objects, like the AsyncTasks, Sockets, etc.


Let’s start with the retained Fragment:
/**
 * This Fragment performs a specific background task and is not
 * destroyed on configuration changes.
 */
public class RetainedFragment extends Fragment {
    /**
     * Interface used by the retained Fragment to communicate with
     * the Activity.
     */
    interface RetainedFragmentCallbacks {
        void onPreExecute();

        void onProgressUpdate(int progress);

        void onCancelled();

        void onPostExecute();
    }

    private RetainedFragmentCallbacks retainedFragmentCallbacks;
    private MyAsyncTask myAsyncTask;

    /**
     * Hold a reference to the parent Activity so we can report the
     * task's current progress and results (the Activity must implement
     * the RetainedFragmentCallbacks interface to receive the data sent
     * by this Fragment).
     * On configuration changes the Android framework passes to this
     * method a reference to the newly created Activity. This way
     * the AsyncTask can deliver its results to the new Activity.
     */
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        retainedFragmentCallbacks = (RetainedFragmentCallbacks) activity;
    }

    /**
     * This method gets invoked when the Fragment is created for
     * first time.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // This Fragment won’t be destroyed on configuration changes.
        setRetainInstance(true);

        // Create and execute the background task.
        myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute();
    }

    /**
     * Set the callback to null so we don't accidentally leak the
     * Activity instance.
     */
    @Override
    public void onDetach() {
        super.onDetach();
        retainedFragmentCallbacks = null;
    }

    /**
     * This AsyncTask performs a long task in the background
     * (we fake it with a sleep) and communicates progress and
     * results to the containing Activity.
     * In each method we check that retainedFragmentCallbacks is
     * not null because the Activity may have been destroyed on
     * configuration changes or because the user exits the Activity
     * pressing the back button.
     */
    private class MyAsyncTask extends AsyncTask {

        @Override
        protected void onPreExecute() {
            if (retainedFragmentCallbacks != null) {
                retainedFragmentCallbacks.onPreExecute();
            }
        }

        /**
         * Note that we do NOT call the callback object's methods
         * directly from the background thread, as this could result
         * in a race condition.
         */
        @Override
        protected Void doInBackground(Void... ignore) {
            for (int i = 0; !isCancelled() && i < 10; i++) {
                SystemClock.sleep(1000);
                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (retainedFragmentCallbacks != null) {
                retainedFragmentCallbacks.onProgressUpdate(progress[0]);
            }
        }

        @Override
        protected void onCancelled() {
            if (retainedFragmentCallbacks != null) {
                retainedFragmentCallbacks.onCancelled();
            }
        }

        @Override
        protected void onPostExecute(Void ignore) {
            if (retainedFragmentCallbacks != null) {
                retainedFragmentCallbacks.onPostExecute();
            }
        }
    }
}

And now the code of the main Activity:
/**
 * This Activity creates a RetainedFragment to perform a background
 * task, and receives progress updates and results from the
 * Fragment when they occur.
 */
public class MainActivity extends AppCompatActivity implements RetainedFragment. RetainedFragmentCallbacks {

    private static final String TAG_RETAINED_FRAGMENT = "RETAINED FRAGMENT";
    private RetainedFragment retainedFragment;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.textView);

        FragmentManager fm = getFragmentManager();
        retainedFragment = (RetainedFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT);

        // If the Fragment is not null, then it is currently being
        // retained across a configuration change.
        if (retainedFragment == null) {
            retainedFragment = new RetainedFragment();
            fm.beginTransaction().add(retainedFragment, TAG_RETAINED_FRAGMENT).commit();
            Log.d(TAG_RETAINED_FRAGMENT, "Retained Fragment created anew");
        }
        else {
            Log.d(TAG_RETAINED_FRAGMENT, "Retained Fragment retained on configuration change");
        }
    }

    // The four methods below are called by the RetainedFragment when new
    // progress updates or results are available. The MainActivity
    // should respond by updating its UI to indicate the change.

    @Override
    public void onPreExecute() {
        Log.d(TAG_RETAINED_FRAGMENT, "onPreExecute() received...");
        textView.setText("onPreExecute() received...");
    }

    @Override
    public void onProgressUpdate(int progress) {
        Log.d(TAG_RETAINED_FRAGMENT, "onProgressUpdate received with progress: " + progress);
        textView.setText("onProgressUpdate received with progress: " + progress);
    }

    @Override
    public void onCancelled() {
        Log.d(TAG_RETAINED_FRAGMENT, "onCancelled() received...");
        textView.setText("onCancelled() received...");
    }

    @Override
    public void onPostExecute() {
        Log.d(TAG_RETAINED_FRAGMENT, "onPostExecute() received...");
        textView.setText("onPostExecute() received...");
    }


}

The code is quite self-explanatory.

When the Activity is created for the first time the method findFragmentByTag returns null, since no Fragment has already been created. As a result a new instance of the RetainedFragment class is created and attached to the main Activity. The main Activity implements RetainedFragment.RetainedFragmentCallbacks interface and is registered to receive updates from the Fragment in the method onAttach.

When the Fragment is created setRetainInstance(true) ensures that it won’t be destroyed on configuration changes. In addition to that the Fragment creates and starts an AsyncTask, defined and managed inside the Fragment class, to perform a long lasting task in the background (for example downloading data from a web service).
Progress updates and final results are delivered to the main Activity through the RetainedFragment.RetainedFragmentCallbacks interface, which is implemented in the Activity (see the logs).

Now let’s suppose that the user rotates the device. This is what happens:

  • the RetainedFragment is not destroyed (because we have called setRetainInstance(true) when the Fragment was created) and the AsyncTask continues to work in the background;
  • the main Activity is destroyed and recreated as usual. When it’s destroyed it’s also detached from the RetainedFragment and the retainedFragmentCallbacks variable inside the Fragment class is set to null as a result. When the Activity is recreated it’s re-attached to the existing Fragment and the retainedFragmentCallbacks variable inside the Fragment is now set to point to the newly created Activity: results and progress updates are therefore delivered to this new Activity, and not to the old one (which doesn’t exist anymore). Notice that the RetainedFragment itself is not recreated because the method findFragmentByTag in onCreate now returns the existing Fragment.

Monday, 23 June 2014

Ordered Broadcasts

There are two types of broadcasts in Android: normal and ordered. The following table shows the main differences:


Normal
Ordered
Way of delivery
Normal broadcasts are delivered to the available receivers asynchronously, in an unspecified order
Ordered broadcasts are delivered to the available receivers one at a time, in a specified order (the order depends on the android:priority attribute of the different receivers in the AndroidManifest file)
Feedback
With normal broadcasts no feedback can be sent to the broadcaster
With ordered broadcasts the receiver can send information to the broadcaster (using the methods abortBroadcast(), seResultCode(), setResultData()


Tip: in a receiver (handling ordered broadcasts) you can call the method abortBroadcast() to make sure that the broadcast is not sent to other receivers. If you combine this method with a high value for the android:priority attribute, you can make sure that your receiver is the only one to handle that broadcast (SMS apps can use this technique).

BroadcastReceiver in Android

BroadcastReceiver

Let’s see how a BroadcastReceiver can handle ordered broadcasts (se the comments in the code for more information):
public class MyReceiver extends BroadcastReceiver {
    [...]
    public void onReceive(Context context, Intent intent) {
        //is this an ordered broadcast or not?
        if(isOrderedBroadcast() {
            //here you can handle the broadcast    
            [...]

            //here you can send information back to the broadcaster
            setResultCode(Activity.RESULT_OK);
            setResultData(“here you can pass a String”);

            //you can also create complex data to send to the broadcaster
            Bundle myBundle = getResultExtras(true);
            myBundle.put [...] //code omitted for brevity
        }
    }
}


Broadcaster

Sending an ordered broadcast is quite simple. Let’s look at the code:
Intent int = new Intent(YOUR_FILTER);
sendOrderedBroadcast(int, responseReceiver, null, RESULT_OK, null, null);
As you can see you just have to create an Intent with the filter registered for the BroadcastReceiver MyReceiver and call the method sendOrderedBroadcast. This method takes a BroadcastReceiver (responseReceiver) as a parameter: this is the receiver that will receive the information sent back by the BroadcastReceiver MyReceiver.
So you finally have to register the BroadcastReceiver responseReceiver (to handle the results) in the following way:
BroadcastReceiver responseReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //here you can retrieve the results sent by MyReceiver
        String str = getResultData();

        //here you can retrieve the complex data sent by MyReceiver
        Bundle myBundle = getResultExtras(false);
        if(myBundle != null) {
            //here you can retrieve the data using myBundle.get... methods
        }
    }
}

Friday, 13 June 2014

Creating a pie chart with aChart Engine

In many Android apps you may need to show data in graphical format (pie charts, bar charts, time charts, etc.). To do this you can write the code from scratch, or use one of the many third-party libraries available. One of the most used is certainly aChart Engine, and there are several good reasons for that:
  1. it is leight;
  2. it is free;
  3. it is relatively complete.

In this brief tutorial I will explain the most important features you must know to create a pie chart using aChart Engine.



Download and install the library

First of all you have to download the library (version 1.1.0) and set up your Eclipse project to use it. The steps are the following:
  1. donwload the library (jar file, version 1.1.0) from this link. You can find the complete documentation of the package here;
  2. open Eclipse; right click on the main project folder; select "Build Path" and "Configure Build Path..."; select "Libraries" tab; click on "Add external Jars..." and select the library Jar file you have downloaded.
Now your Eclipse project is ready to use the library. Of course, if you want to use the different classes, you first have to import them in your Activity (import org.achartengine.model.CategorySeries ... etc.). 

Source data

Let’s suppose we want to create a pie chart showing the distribution of the different Android versions, using the following data:
Kit Kat – 7.8%
Jelly Bean – 33%
Ice Cream Sandwich – 25%
Honeycomb – 17%
Gingerbread – 11%
Older – 6.2%

Each slice of the pie chart is represented by a different color.
The source data in Java is represented by three arrays, each with six elements:

String[] labels = new String[] {“Kit Kat”, “Jelly Bean”, “Ice Cream Sandwich”, “Honeycomb”, “Gingerbread”, “Older”};
double[] values = {7.8, 33, 25, 17, 11, 6.2};
int[] colors = {Color.RED, Color.BLUE, Color.GREEN, Color.MAGENTA, Color.YELLOW, Color.GRAY};

CategorySeries

The class CategorySeries of aChart Engine (org.achartengine.model.CategorySeries) is used to represent a series for the category charts like the pie ones.
We create an instance of this class and populate it with the previously showed source data (labels and values):

CategorySeries categorySeries = new CategorySeries(“Android versions”);
for(int i=0; i<labels.length; i++) {
    categorySeries.add(labels[i], values[i]);
}

DefaultRenderer

The class DefaultRenderer (org.achartengine.renderer.DefaultRenderer) is an abstract renderer used to format the charts.
We can create an instance of this class and specify how we want our pie chart to be rendered:

DefaultRenderer defaultRenderer = new DefaultRenderer();
defaultRenderer.setChartTitle(“Android versions”);
defaultRenderer.setChartTitleTextSize(30);
defaultRenderer.setZoomButtonsVisible(false);
...
NB: It is important to set the zoom buttons visibility to false, otherwise you may get a NullPointerException in the onDraw() method of the class GraphicalView (as far as I know, this should be a bug in the library).
The are of course many other formattings you can control using a DefaultRenderer: you can read the official documentation for more information.

To control the rendering of each slice of the pie chart (color, showing or not showing values, etc.) you can use the SimpleSeriesRenderer class (org.achartengine.renderer.SimpleSeriesRenderer): you just have to create an instance of this class for each slice, set the relevant data and add it to the previously defined DefaultRenderer:

for(int i=0; i<labels.length; i++) {
    SimpleSeriesRenderer simpleSeriesRenderer = new SimpleSeriesRenderer();
    simpleSeriesRenderer.setColor(colors[i]);
    simpleSeriesRenderer.setDisplayChartValues(true);
    ...
    defaultRenderer.addSeriesRenderer(simpleSeriesRenderer);
}

Starting a new Activity

To show our pie chart we first have to retrieve an Intent, populated with the relevant data, using the ChartFactory class:

Intent intPiechart = ChartFactory.getPieChartIntent(context, categorySeries, defaultRenderer, “Android versions”);
startActivity(intPieChart);

The new Activity must be declared in the AndroidManifest.xml file as well:

<activity android:name=”org.achartengine.GraphicalActivity”/>

Monday, 5 May 2014

Spinner with "re-select" functionality

This is an advanced tutorial: I'll cover custom views, the Android source code and Java reflection.

A simple Spinner

As you probably already know, when the user selects an item from a Spinner the event onItemSelected is triggered.
To define the selection event handler for a Spinner, you have to implement the AdapterView.OnItemSelectedListener interface and the corresponding onItemSelected() callback method:
public class SpinnerActivity extends Activity implements OnItemSelectedListener {
 //...
 
    public void onItemSelected(AdapterView<?> parent, View view,
            int pos, long id) {
        // An item was selected. You can retrieve the selected item using
        // parent.getItemAtPosition(pos)
    }

    public void onNothingSelected(AdapterView<?> parent) {
        // Another interface callback
    }
}

However, if the Spinner is set to a specific item and the user re-selects the same item, no callback method is invoked. The reason is that the default implementation of the Spinner class uses an internal variable to keep track of the currently selected item: if the user re-selects the same item, no callback method is triggered.
To be more precise, to understand exactly what happens we have to look at the source code of the Spinner class, using AndroidXRef:

By examining the source code you can discover that when the user selects an item the method setSelection is invoked (check the methods onClick and show) . The method setSelection is defined in the superclass AbsSpinner (Spinner infact extends AbsSpinner).


As you can see the method setSelection calls setSelectionInt, which checks if the currently selected item is equal to the item already selected: in this case no event is triggered.
From the source code of setSelectionInt we discover that the class uses the variable mOldSelectedPosition to keep track of the already selected item of the Spinner. However, the AbsSpinner class contains no definition of this variable, so we have to check the superclasses.
After a brief search you can discover that mOldSelectedPosition is defined in the class AdapterView (superclass of AbsSpinner):


Now we have all the elements to create our custom Spinner with "re-select" functionality:
import java.lang.reflect.Field;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Spinner;


public class SpinnerReselect extends Spinner {
 
 public SpinnerReselect(Context context) {
     super(context);
     // TODO Auto-generated constructor stub
 }
 
 public SpinnerReselect(Context context, AttributeSet attrs) {
     super(context, attrs);
     // TODO Auto-generated constructor stub
 }
 
 public SpinnerReselect(Context context, AttributeSet attrs, int defStyle) {
     super(context, attrs, defStyle);
     // TODO Auto-generated constructor stub
 }

  @Override
 public void setSelection(int position, boolean animate) {
     ignoreOldSelectionByReflection();
     super.setSelection(position, animate);
 }

  private void ignoreOldSelectionByReflection() {
     try {
         Class<?> c = this.getClass().getSuperclass().getSuperclass().getSuperclass();
         Field reqField = c.getDeclaredField("mOldSelectedPosition");
         reqField.setAccessible(true);
         reqField.setInt(this, -1);
     } catch (Exception e) {
         Log.d("Exception Private", "ex", e);
         // TODO: handle exception
     }
 }

  @Override
 public void setSelection(int position) {
     ignoreOldSelectionByReflection();
     super.setSelection(position);
 }
}



By implementing our custom Spinner we have to override the default implementation of the setSelection methods. To do so, we simply call the method ignoreOldSelectionByReflection before calling the default methods (defined in the class AbsSpinner: that's why we use super.setSelection...).

In the method ignoreOldSelectionByReflection we make use of Java reflectionReflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

Let's examine the code:

Class<?> c = this.getClass().getSuperclass().getSuperclass().getSuperclass();

As we already saw the variable mOldSelectedPosition is defined the class AdapterView (superclass of AbsSpinner). So we have to get a reference to this class by calling getClass and getSuperclass respectively.


Field reqField = c.getDeclaredField("mOldSelectedPosition");
reqField.setAccessible(true);

The secondo step is to get access to the field mOldSelectedPosition. By calling reqField.setAccessible(true) we make sure that the reflected object should suppress Java language access checking when it is used.

reqField.setInt(this, -1);

The trick is quite clear: we set the value of mOldSelectedPosition to -1 to make sure that the currently selected item of the Spinner is always different from the previously selected item, even if the user re-selectes the same item.
This way the method setSelection is always triggered!


Sunday, 27 April 2014

ListPreference: how to load data dinamically

In this tutorial I'll explain how to populate a ListPreference programmatically. It is easier than you might expect; however I haven't found any official tutorial about it. If you need more information about Settings in Android you can read the official documentation.


Generally speaking, if you want to add a ListPreference to your app's settings, you have to add a ListPreference object in the preference XML file, like in the following example:
<ListPreference
        android:dependency="pref_sync"
        android:key="pref_syncConnectionType"
        android:title="@string/pref_syncConnectionType"
        android:dialogTitle="@string/pref_syncConnectionType"
        android:entries="@array/pref_syncConnectionTypes_entries"
        android:entryValues="@array/pref_syncConnectionTypes_values"
        android:defaultValue="@string/pref_syncConnectionTypes_default" />

As you can see you provide the entries of the ListPreference, and the corresponding values, with the items android:entries and android:entryValues, that refer to an array loaded in the res/values folder.
But if you want to load the data programmatically (for example, if you want to fetch the data from a local database or from an online service), you must create a custom ListPreference class.

To begin with, let's see the new preference XML file with a reference to our custom ListPreference:
<com.androidthetechnicalblog.preference.MyCustomPreference
            android:key="pref_mycustompreference"
            android:title="@string/pref_mycustompreference"
            android:summary="@string/pref_mycustompreference_summary"/>

We are creating a custom ListPreference class, so we must provide the full path of the class. As you can also see, we omitted android:entries and android:entryValues, because we want to load the data programmatically.

Now let's see how MyCustomPreference class looks like (not relevant code omitted for brevity):
public class MyCustomPreference extends ListPreference {  
    // ...  

    public MyCustomPreference (Context context, AttributeSet attrs) {      
        super(context, attrs);      
    
        setEntries(entries());         
        setEntryValues(entryValues());         
        setValueIndex(initializeIndex());       
    }  

    public MyCustomPreference (Context context) {      
        this(context, null);  
    }  

    private CharSequence[] entries() {      
        //action to provide entry data in char sequence array for list          
        String myEntries[] = {"one", "two", "three", "four", "five"};         

        return myEntries;  
    }  

    private CharSequence[] entryValues() {      
        //action to provide value data for list           
     
        String myEntryValues[] = {"ten", "twenty", "thirty", "forty", "fifty"};
        return myEntryValues;
   }

   private int initializeIndex() {
        //here you can provide the value to set (typically retrieved from the SharedPreferences)
        //...

        int i = 2;
        return i;
    }
}

The code is quite simple. You just have to provide the entries and entryValues through the methods setEntries and setEntryValues, that accept a CharSequence (or String) array as a parameter. You can also set the default initial value, typically retrieved from the SharedPreferences, through the method setValueIndex

For everything that is not explicitly covered in this tutorial you can refer to the official documentation aboud Android settings.

Wednesday, 23 April 2014

Animating a ProgressBar to a specific value

In this tutorial I'll explain how to set a specific progress value to a ProgressBar using a smooth animation.



ObjectAnimator animation = ObjectAnimator.ofInt(pbBudget, "progress", 0, 50);
To begin with we create an ObjectAnimator object: this is a subclass of ValueAnimator that provides support for animating properties on target objects. The constructors of this class take parameters to define the target object that will be animated as well as the name of the property that will be animated. In this example we used the following parameters:
  • pbBudget: reference to the ProgressBar in the layout;
  • "progress": the name of the property to be animated;
  • 0: starting point of the animation;
  • 50: ending point of the animation.

animation.setDuration(1500);
The code is self-explanatory: the animation lasts 1,5 seconds.


animation.setInterpolator(new DecelerateInterpolator());
It is possible to use different interpolators for our animation, like for example:

animation.start();

You can also create an animation for the ProgressBar from scratch, using the method setProgress(int progress) with a delay (android.os.SystemClock.sleep(long ms)).
If you decide to do so, I suggest to use an AsyncTask and update the ProgressBar using the onProgressUpdate method.
Avoid animating the ProgressBar in the UI thread with a loop:
  1. because this will freeze the UI until the animation is completed;
  2. because even though you are in the UI thread, you don't release the UI thread until the animation is completed, thus preventing the system to update the UI (and the ProgressBar) itself.

Wednesday, 16 April 2014

Hidden Android APIs: hiding SMS messages to the default SMS receiver

In a previous tutorial we saw how to take advantage of the hidden Android APIs to listen to incoming SMS messages.
Now suppose that we want to listen to specific SMS messages (messages coming from a particular number or containing specific strings) and to hide them to the default SMS receiver (like Google Hangout or other client).

This is a private message!

As in the previous tutorial we have to declare a BroadcastReceiver in the AndroidManifest.xml file. However, in order to take priority over the default SMS receiver, we also have to set a high priority for our SMS BroadcastReceiver:
<receiver android:name="com.androidthetechinalblog.SMSReceiver">
   <intent-filter android:priority="9999">
      <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
   </intent-filter>
</receiver>

Now we just have to make some minor changes to the SMSReceiver class:
public class SMSReceiver extends BroadcastReceiver {
 
 public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();
 
  if(action.equals(“android.provider.Telephony.SMS_RECEIVED”)) {
   Object incomingSMSs[] = (Object[]) intent.getSerializableExtra(“pdus”);
    
   for(Object tmp : incomingSMSs) {
    byte message[] = (byte[]) tmp;
    SmsMessage smsMessage = SmsMessage.createFromPdu(message);

    String phoneNumber = smsMessage.getOriginatingAddress();
    String messageBody = smsMessage.getMessageBody();

    if(phoneNumber.equals("3457148596") || messageBody.contains("test")) {
       //we have found our SMS! here you can perform some actions
       abortBroadcast();
       setResultData(null);
    }
   }
  }
 }
}

As you can see, by calling abortBroadcast() and setResultData(null) (methods working only for "ordered broadcasts", like in our example) we make sure that our SMS won't be propagated to any other receiver.