13

I have following ContentObserver implementation for receiving and writing SMS, but it is called multiple times.

Code:

public class SMSObserverActivity extends Activity {
    protected MyContentObserver observer = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        String url = "content://mms-sms/";
        Uri uri = Uri.parse(url);
        observer = new MyContentObserver(new Handler());
        getContentResolver().registerContentObserver(uri, true, observer);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();

        getContentResolver().unregisterContentObserver(observer);
    }

    class MyContentObserver extends ContentObserver {
        ContentValues values = new ContentValues();
        Handler handler;

        public MyContentObserver(Handler handler){
            super(handler);
            this.handler = handler;
        }

        @Override
        public boolean deliverSelfNotifications(){
            return false;
        }


        @Override
        public void onChange(boolean arg0){
            super.onChange(arg0);

            Log.v("SMS", "Notification on SMS observer");
            values.put("status", 5);
            Message msg = new Message();
            msg.obj = "xxxxxxxxxx";
            int threadId = 0;
            handler.sendMessage(msg);

            Uri uriSMSURI = Uri.parse("content://sms/");
            Cursor cur =
                    getContentResolver().query(uriSMSURI, null, null, null,
                            null);
            cur.moveToNext();
            Log.e("sms", cur.getString(4)+" "+cur.getString(11));
        }
    }
}

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test"
    android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.READ_SMS"></uses-permission>
    <uses-permission android:name="android.permission.WRITE_SMS"></uses-permission>

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".SMSObserverActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

Why is it called multiple times?

EDIT:
There was the idea that the problem is caused by the missing unregisterContentObserver, but it makes no difference.

CSchulz
  • 10,882
  • 11
  • 60
  • 114

3 Answers3

11

This is occurring because you are registering your content observer for the entire SMS database. So your content observer gets notified each time a table entry in the database gets updated.

In this case when a message is sent for example around 7 tables entries get updated so your content observer gets notified 7 times.

Since I'm only interested if a message is sent I've changed to only observe the queued messages and this means my observer always gets notified exactly three times so I have implemented code to protect against that.

There are likely to be some other issues such as multi recipient or multi part messages but the basics work so far.

Donal Rafferty
  • 19,707
  • 39
  • 114
  • 191
  • Okay I thought the observer is only called one time for all related updates per object. I think there is an unique ID to see, which notifications belongs to one update, isn't it? – CSchulz Feb 03 '12 at 11:26
2

To avoid sending multiple sms by content observer try this

public class SmsObserver extends ContentObserver {
    SharedPreferences trackMeData;
    private Context context;
    private static int initialPos;
    private static final String TAG = "SMSContentObserver";
    private static final Uri uriSMS = Uri.parse("content://sms/sent");

    public SmsObserver(Handler handler, Context ctx) {
        super(handler);
        context = ctx;
        trackMeData = context.getSharedPreferences("LockedSIM", 0);
        initialPos = getLastMsgId();

    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        queryLastSentSMS();
    }

    public int getLastMsgId() {

        Cursor cur = context.getContentResolver().query(uriSMS, null, null, null, null);
        cur.moveToFirst();
        int lastMsgId = cur.getInt(cur.getColumnIndex("_id"));
        Log.i(TAG, "Last sent message id: " + String.valueOf(lastMsgId));
        return lastMsgId;
    }

    protected void queryLastSentSMS() {

        new Thread(new Runnable() {

            @Override
            public void run() {
                Cursor cur =
                    context.getContentResolver().query(uriSMS, null, null, null, null);

                if (cur.moveToNext()) {



                    try {

                        String body = cur.getString(cur.getColumnIndex("body"));

                        if (initialPos != getLastMsgId()) {

                            String receiver = cur.getString(cur.getColumnIndex("address"));
                            Log.i("account", myDeviceId);
                            Log.i("date", day + "-" + month + "-" + year + " "
                                + hour + ":" + minute + ":" + seconde);
                            Log.i("sender", myTelephoneNumber);
                            Log.i("receiver", receiver );


                            // Then, set initialPos to the current position.
                            initialPos = getLastMsgId();

                            sendsmstoph(receiver, body);
                        }
                    } catch (Exception e) {
                        // Treat exception here
                    }
                }
                cur.close();
            }
        }).start();

    }
arjun
  • 3,514
  • 4
  • 27
  • 48
  • Calling initialPos = getLastMsgId(); from contructor throws null pointer exception – Udi Oshi Feb 12 '14 at 09:42
  • I have noticed that this is good but not always fool proof. So I think that using SharedPreferences can solve this problem. Here's how I have done it: if (!sharedPreferences.getString("lastMessageTime", "0") .equals(String.valueOf(timeInMillis)) || !sharedPreferences.getString("lastMessageNumber", "0") .equals(number)) – Usman Apr 25 '17 at 08:34
0

If you want to have your observer enabled only when the activity is in active state, I advise you to move registerContentObserver() and unregisterContentObserver() to methods onResume() and onPause() respectively. onDestroy() may not be called if your application exits, but onPause() is guaranteed to be.

Malcolm
  • 41,014
  • 11
  • 68
  • 91
  • The observer shall be always active, when the *Activity* is running. It is just a observing content test. – CSchulz Nov 21 '11 at 11:12
  • Activity is running between `onResume()` and `onPause()`, in the other time it is either in background or not visible. It may even get killed without getting a call to `onDestroy()`, and when you recreate application in `onCreate()`, you will register an observer for the second time. – Malcolm Nov 21 '11 at 11:20
  • Why shall it registered two times? I have an unregister statement. – CSchulz Nov 21 '11 at 11:21
  • You have it in `onDestroy()` method, and it is not guaranteed to be called. The method which gets called when your activity stops running (goes off the screen) is `onPause()`. From documentation about `onDestroy()`: "there are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it". – Malcolm Nov 21 '11 at 11:25
  • Okay I have tried it now with a static class member, which contains the observer. It makes no difference. Using `onResume()` and `onPause()` isn't suitable for me, because I want to start the `Activity` and after that I send a sms to me. – CSchulz Nov 21 '11 at 11:42
  • I'm afraid I didn't quite get why do you think that making observer static would make difference? And if you want to receive events while activity is in the background, then you shouldn't use an activity, you should use a service. – Malcolm Nov 21 '11 at 11:54