I have defined the following service with an observer of messages sent. The problem is that when sending a message, I sense that is called 3 times onChange method of contentobserver. ¿Someone know tell me why?
Thanks
public class DSMSService extends Service {
private static final String CONTENT_SMS = "content://sms";
private class MyContentObserver extends ContentObserver {
ContentValues values = new ContentValues();
int threadId;
public MyContentObserver() {
super(null);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.v(TAG, "****************************** SMS change detected *************************************");
Log.v(TAG, "Notification on SMS observer");
// save the message to the SD card here
Uri uriSMSURI = Uri.parse("content://sms");
Cursor cur = getBaseContext().getContentResolver().query(uriSMSURI, null, null, null, null);
// this will make it point to the first record, which is the last SMS sent
cur.moveToNext();
String content = cur.getString(cur.getColumnIndex("body"));
Log.v(TAG, "content: " + content);
}
#Override
public boolean deliverSelfNotifications() {
return false;
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
Log.v(TAG, "starting........");
MyContentObserver contentObserver = new MyContentObserver();
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse("content://sms"),true, contentObserver);
DAO = new DAOaBlackList(getBaseContext());
}
#Override
public void onDestroy() {
Log.d(TAG, "stopping........");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "Received start id " + startId + ": " + intent);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startid) {
Log.v(TAG, "onStart........");
}
}
What you want to do is check for the _id of the last item in the content://sms/sent uri inside onChange. You need to store the previous _id (maybe in a static global variable) and compare it to the _id of the last item (cursor.moveToLast())of the cursor after you query for content://sms/sent. If the _id is the same, you can choose to ignore the call to onChange. This multiple calls to onChange I believe is due to the sms being moved from folder to folder during sending - outbox, sent items, some other "invisible folder" (which we can't know exactly what, as this particular feature REALLY REALLY needs proper documentation). As you cannot listen to a more specific Uri than content://sms/sent you'll have to implement this checking for _id everytime you want to detect an sms being sent.
If the previous _id is different from the one in your static global variable, then you have an sms being sent.
You have kept the Observer for the SMS database through URI. so whenever message is being send the database is updated and 3 of the column of that table is getting updated. so it will notify the observer for each of them. so it is being called for as many times as table data is updated.
Related
I want to read the sent message content from the mobile messages. I am not developing an sent sms reading application, but instead I want to read the recent last sent sms content form the mobile inbuilt sms app.
I want to read the sent sms from sent items and send some notification based on the keyword in the message.
I know we have to extend ContentObserver class and use ContentResolver.
Any idea is appreciated. Thank you!
This is my Observer class SMSObserver.java,
public class SMSObserver extends ContentObserver {
private String lastSmsId;
private Context c;
private String phoneNumber;
int type;
String lastID;
public SMSObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange,Uri uri) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse("content://sms/out");
Cursor cur = c.getContentResolver().query(uriSMSURI, null, null, null, "date DESC LIMIT 1");
cur.moveToNext();
//String id = cur.getString(cur.getColumnIndex("_id"));
if( (type == 2 || type == 1) && (!lastID.contentEquals(cur.getString(cur.getColumnIndex("_id")))) ) {
String protocol = cur.getString(cur.getColumnIndex("protocol"));
lastID = cur.getString(cur.getColumnIndex("_id"));
// Message sent
if (protocol == null) {
Log.i("SMSStatus", "SMS Sent");
}
// Message receive
else {
Log.i("SMSStatus", "SMS received");
}
if (smsChecker(lastID)) {
String address = cur.getString(cur.getColumnIndex("address"));
// Optional: Check for a specific sender
if (address.equals(phoneNumber)) {
String message = cur.getString(cur.getColumnIndex("body"));
// Use message content for desired functionality
if(message.contains("Dinner")){
Toast.makeText(c,"Dinner offer for 2!!",Toast.LENGTH_LONG).show();
}
}
}
}
}
// Prevent duplicate results without overlooking legitimate duplicates
public boolean smsChecker(String smsId) {
boolean flagSMS = true;
if (smsId.equals(lastSmsId)) {
flagSMS = false;
}
else {
lastSmsId = smsId;
}
Log.d(lastSmsId ,"LastSmsId");
return flagSMS;
}
}
I'm registering the contentObserver in a class SentSMSTrackerService.java
public class SentSMSTrackerService extends Service {
#Override
public int onStartCommand(Intent intent, int flag, int startId) {
SMSObserver smsObserver = new SMSObserver(new Handler());
ContentResolver contentResolver = getApplicationContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse("content://sms/out"), true, smsObserver);
return START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I'm also giving the permission in AndroidManifest.xml
But,still i am not able to read the outgoing sms.
I am developing an app that notify the user when any SMS marked as read even if the app isn't running
I simply created a contentobserver and I registered it in a service
the problem is that the contentobserver runs if the new SMS inserted or deleted but when the SMS marked as read ( Update operation) it doesn't work
here is my service code
public class Smssendservice extends Service {
private static Timer timer = new Timer();
private Context ctx;
public IBinder onBind(Intent arg0)
{
return null;
}
public void onCreate()
{
super.onCreate();
ctx = this;
startService();
}
private void startService()
{
//timer.scheduleAtFixedRate(new mainTask(), 0, 5000);
Toast.makeText(getApplicationContext(), "Before Register", Toast.LENGTH_SHORT).show();
final Uri SMS_STATUS_URI = Uri.parse("content://sms");
SMSLogger sl= new SMSLogger();
SMSObserver smsSentObserver = new SMSObserver(sl, ctx);
getContentResolver().registerContentObserver(SMS_STATUS_URI, true, smsSentObserver);
Toast.makeText(getApplicationContext(), "After Register", Toast.LENGTH_SHORT).show();
}
}
I am registering my content observer in the service
here is the content observer code
public class SMSObserver extends ContentObserver
{
SMSLogger smsLogger;
Context context;
public SMSObserver(SMSLogger smsLogger, Context context) {
super(new Handler());
this.context=context;
this.smsLogger = smsLogger;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
smsLogger.querySMS(context);
}
}
eventually here is the SMS logger that I show the TOAST if the SMS data changed
public class SMSLogger {
protected void querySMS(Context context) {
Uri uriSMS = Uri.parse("content://sms/");
Cursor cur = context.getContentResolver().query(uriSMS, null, null, null, null);
/* cur.moveToNext(); // this will make it point to the first record, which is the last SMS sent
String body = cur.getString(cur.getColumnIndex("body")); //content of sms
String add = cur.getString(cur.getColumnIndex("address")); //phone num
String time = cur.getString(cur.getColumnIndex("date")); //date
String protocol = cur.getString(cur.getColumnIndex("protocol")); //protocol*/
Toast.makeText(context, "Data Changed CHECK SMS" , Toast.LENGTH_SHORT).show();
/*logging action HERE...*/
}
}
it showed this message "Data Changed CHECK SMS" if new SMS inserted or SMS deleted but in case of update the toast doesnt appear. any clue ?
In your update method, check if the number of entries updated is more than 0.
If it is, do getContext().getContentResolver().notifyChange(uri, null); before you return the number of entries updated.
What I want to do is to have a Service with more than one ContentObserver registered and check which ContentObserver triggers onChange() to do specific reactions. I dont know if I just have to include a if/else inside the onchange(), or Overwrite it to each ContentObserver, in either cases I wouldnt know exactly how to do it. Thanks in advance for any help.
public class SmsObserverService extends Service {
private String BABAS = "babas";
#Override
public void onCreate() {
Handler handler = new Handler();
this.getContentResolver().registerContentObserver(Uri.parse("content://sms/"), true, new SmsObserver(handler));
//Second observer
this.getContentResolver().registerContentObserver(Uri.parse("CallLog.Calls.CONTENT_URI"), true, new SmsObserver(handler));
}
#Override
public IBinder onBind(Intent intent) {
// TODO Put your code here
return null;
}
public class SmsObserver extends ContentObserver{
public SmsObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//Where I should check somehow which ContentObserver triggers the onChange
//This code to the sms log stuff, the call log part will be included
//when I find out how to check whic observer trigerred the onChange
Uri uri = Uri.parse("content://sms/");
Cursor cursor = getApplicationContext().getContentResolver().query( uri, null, null, null, null);
cursor.moveToNext();
String body = cursor.getString(cursor.getColumnIndex("body"));
String add = cursor.getString(cursor.getColumnIndex("address"));
String time = cursor.getString(cursor.getColumnIndex("date"));
String protocol = cursor.getString(cursor.getColumnIndex("protocol"));
if(protocol == null){
Toast.makeText(getApplicationContext(), "Enviada para: " +add + ", Hora: "+time +" - "+body, Toast.LENGTH_SHORT).show();
Log.i(BABAS, "Enviada para: "+add +" " +"Time: "+time +" - "+body);
}else{
Toast.makeText(getApplicationContext(), "Recebida de: "+add + ", Hora: "+time +" - "+body, Toast.LENGTH_SHORT).show();
Log.i(BABAS, "Recebida de: "+add +" " +"Time: "+time +" - "+body);
}
}
}
}
I'd simply extend ContentObserver and add whatever I am missing there.
mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
#Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
}
}
};
You can check uri with above method
I am reading first 100 SMS from inbox and store it in local database. I am doing this with the help on AsyncTask. Afterwards I am reading those SMS from local database and display it. In onPostExecute of AysncTask after reading & storing first 100 SMS, I am invoking the service to read the remaining SMS and storing it in local database. Till before invoking the service, UI is responsive but after invoking the service, the UI became unresponsive. Is it due to database lock or something else. Please help to solve this problem...
My AsyncTask code
private class InitialSetup extends AsyncTask<String, Integer, Long> {
#Override
protected Long doInBackground(String... urls) {
fetchSMS();
updateDatabase();
return 0L;
}
#Override
protected void onPostExecute(Long result) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
populateUI(getApplicationContext());
Intent intent = new Intent(getApplicationContext(), SMSService.class);
getApplication().startService(intent);
}
}
Service code
public class SMSService extends Service {
Long inboxSMSID, localSMSID;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onStart(Intent intent, int startid) {
if (intent.hasExtra("InboxSMS") && intent.hasExtra("SMSID")) {
inboxSMSID = intent.getLongExtra("InboxSMS", 0);
localSMSID = intent.getLongExtra("SMSID", 0);
globalToLocalSMSDB(inboxSMSID);
}
}
private void globalToLocalSMSDB(Long id) {
SMSTable smsObj = new SMSTable(getApplicationContext());
smsObj.open();
Uri uriSMSURI = Uri.parse("content://sms/inbox");
Cursor smscur = getContentResolver().query(uriSMSURI, null,
"_id < " + id, null, "_id DESC");
while (smscur.moveToNext()) {
String sender = SMSManagement.getSender(smscur);
String message = SMSManagement.getMessage(smscur);
String timeStamp = SMSManagement.getTime(smscur);
smsObj.insertIntoSMSTable(sender, message, timeStamp);
}
smscur.close();
smsObj.close();
}
}
The service is running in UI thread as well, you have to create worker threads there manually unless it's an IntentService.
Is it possible to intercept outgoing SMS before it is actually sent, get its contents then ignore / send it according to some criteria?
eg. block all international text (numbers with leading 00), but allow everything else.
Incoming SMS
You can intercept an incoming sms thru sms listener using Broadcast receiver.You can modify the incoming sms or destroy it so that it does not reaches inbox.
Outgoing SMS
You can listen for outgoing sms by putting content observer over content://sms/out but you can not modify it with the native sms app.You can obviously modify the content of content://sms/out but it has no point.
Based on what I've been able to find, it seems as though the answer is either, "It's impossible" or, that it could be possible, but you'd need to write your own SMS app, so that you received the text before it became an SMS, and then you could perform whatever checks you'd like on it before calling the API to actually queue it to be sent.
Sorry =(
As far as I know, you can spy on outgoing SMS messages but you cannot stop them from being sent out.
Here's how you can detect the outgoing SMS messages:
Listen outgoing SMS or sent box in Android
But since this is done basically by reading from a database, I doubt you can stop the SMS from leaving.
I wish you good luck.
Emmanuel
This is what i have done to make an OutgoingSMSReceiver hope it helps some one some dya!
public final class OutgoingSMSReceiver extends Service {
private static final String CONTENT_SMS = "content://sms/";
private CallerHistoryDataSource database = new CallerHistoryDataSource(UCDGlobalContextProvider.getContext());
static String messageId="";
private class MyContentObserver extends ContentObserver {
public MyContentObserver() {
super(null);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse(CONTENT_SMS);
Cursor cur = UCDGlobalContextProvider.getContext().getContentResolver().query(uriSMSURI, null, null, null, null);
// this will make it point to the first record, which is the last SMS sent
cur.moveToNext();
String message_id = cur.getString(cur.getColumnIndex("_id"));
String type = cur.getString(cur.getColumnIndex("type"));
if(type.equals(Constants.SMS_TYPE_OUTGOING)){
/**
* onChange is fired multiple times for a single SMS, this is to prevent multiple entries in db.
*
*/
if(!message_id.equals(messageId))
{
String content = cur.getString(cur.getColumnIndex("body"));
String msisdnWithCountryCodeOrPrefix = cur.getString(cur.getColumnIndex("address"));
String msisdn = MSISDNPreFixHandler.fixMsisdn(msisdnWithCountryCodeOrPrefix);
Sms sms = new Sms();
sms.setType(Constants.SMS_TYPE_OUTGOING);
sms.setMsisdn(msisdn);
sms.setContent(content);
Log.i("MyContentObserver", "Sent SMS saved: "+content);
}
messageId = message_id;
}
}
#Override
public boolean deliverSelfNotifications() {
return false;
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
MyContentObserver contentObserver = new MyContentObserver();
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse(CONTENT_SMS),true, contentObserver);
//Log.v("Caller History: Service Started.", "OutgoingSMSReceiverService");
}
#Override
public void onDestroy() {
//Log.v("Caller History: Service Stopped.", "OutgoingSMSReceiverService");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Log.v("Caller History: Service Started.", "OutgoingSMSReceiverService");
/**
* Constant to return from onStartCommand(Intent, int, int): if this service's process is killed while it is started
* (after returning from onStartCommand(Intent, int, int)), then leave it in the started state but don't retain this delivered intent.
* Later the system will try to re-create the service. Because it is in the started state, it will guarantee to call
* onStartCommand(Intent, int, int) after creating the new service instance; if there are not any pending start commands to be
* delivered to the service, it will be called with a null intent object, so you must take care to check for this.
* This mode makes sense for things that will be explicitly started and stopped to run for arbitrary periods of time, such as a
* service performing background music playback.
*/
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startid) {
Log.v("Caller History: Service Started.", "OutgoingSMSReceiverService");
}
}
Based on "Saad Akbar" response , i make it work but only with rooted device with permission MODIFY_PHONE_STATE
public class OutgoingSMSReceiver extends Service
{
private static final String CONTENT_SMS = "content://sms/";
static String messageId = "";
private class MyContentObserver extends ContentObserver
{
Context context;
private SharedPreferences prefs;
private String phoneNumberBlocked;
public MyContentObserver(Context context) {
super(null);
this.context = context;
}
#Override
public void onChange(boolean selfChange)
{
super.onChange(selfChange);
prefs = context.getSharedPreferences("com.example.testcall", Context.MODE_PRIVATE);
phoneNumberBlocked = prefs.getString("numero", "");
Uri uriSMSURI = Uri.parse(CONTENT_SMS);
Cursor cur = context.getContentResolver().query(uriSMSURI, null, null, null, null);
if (cur.moveToNext())
{
String message_id = cur.getString(cur.getColumnIndex("_id"));
String type = cur.getString(cur.getColumnIndex("type"));
String numeroTelephone=cur.getString(cur.getColumnIndex("address")).trim();
if (numeroTelephone.equals(phoneNumberBlocked))
{
if (cur.getString(cur.getColumnIndex("type")).equals("6"))
{
ContentValues values = new ContentValues();
values.put("type", "5");
context.getContentResolver().update(uriSMSURI,values,"_id= "+message_id,null);
}
else if(cur.getString(cur.getColumnIndex("type")).equals("5"))
{ context.getContentResolver().delete(uriSMSURI,"_id=?",new String[] { message_id});
}
}
}
}
#Override
public boolean deliverSelfNotifications()
{
return false;
}
}
#Override
public void onCreate()
{
MyContentObserver contentObserver = new MyContentObserver(getApplicationContext());
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse(CONTENT_SMS), true, contentObserver);
}
}
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />