Catch sent SMS (Android 2.2) - android

I know that there are a few Questions here on SO relating to this, but none of them helped me to get this working - capture SMS that being sent.
I am using Android 2.2 (FROYO) on a Samsung phone (if that matters somehow).
I've searched a lot for this on Stackoverflow and realized that I need ContentObserver for my request. I'm using Service instead of Activity, so I've registered that ContentObserver in my Service class, and it looks like this:
public class SMSSending extends Service {
private class MyContentObserver extends ContentObserver {
public MyContentObserver() {
super(null);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse("content://sms/sent");
Cursor cur = getBaseContext().getContentResolver().query(uriSMSURI, null, null, null, null);
cur.moveToNext();
String content = cur.getString(cur.getColumnIndex("body"));
Toast.makeText(getApplicationContext(), "SOME TEXT", Toast.LENGTH_LONG).show();
}
#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/sent"),true, contentObserver);
Toast.makeText(getApplicationContext(), "SERVICE CREATED", Toast.LENGTH_LONG).show();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startid) {
Toast.makeText(getApplicationContext(), "SERVICE STARTED", Toast.LENGTH_LONG).show();
}
}
As you can see I've put Toast in few places so I could see if this is working at all - and unfortunately none of this notifications appear. Also, i tried with putting some code for LogCat but nothing happens.
I've also tried to put Uri uriSMSURI = Uri.parse("content://sms"); instead of content://sms/sent
but the application simply doesn't do anything.
Of course, I have permissions in Manifest:
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
What am i missing?

Fortunately, I've managed to work it out, but by using totally different approach. Maybe is this going to help someone in future..
Instead of using ContentObserver (which I still don't know why didn't work) I've created new Thread and started it after my service has been created and started. So it looks like this:
...
final Uri CONTENT_URI = Uri.parse("content://sms/sent");
...
public void onStart(Intent intent, int startid) {
Go();
}
private void Go(){
new Thread(new Runnable() {
public void run() {
try {
while(true){
Cursor cursor = getContentResolver().query(CONTENT_URI, null, null, null, null);
if(cursor.moveToFirst()){
text = cursor.getString(cursor.getColumnIndexOrThrow("body")).toString();
if(!text.equalsIgnoreCase(actual)){
previous = text;
//do what you need..
}
}
Thread.sleep(60000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
It's working absolutely stable, even better than with using ContentObserver, having in mind that lot of people had problems with it, something like this and some other..

Related

FIleObserver and ContentObserver not working in Android Marshmallow

I have issue with both FIleObserver and ContentObserver not working in Android Marshmallow. I am using this thing for detecting changes that happening inside a folder. I set run time permissions for marshmallow. But after that also it shows no events. It works perfectly in other versions. Please help me to solve this problem.
First I tried Content Resolver inside Service for detect folder changes in background.
public class TestService extends Service {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
initial();
return START_STICKY;
}
public void initial(){
getContentResolver().registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
true,
new ContentObserver(new Handler()) {
#Override
public boolean deliverSelfNotifications() {
Log.d("hai", "deliverSelfNotifications");
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
#Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.toString().matches(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/[0-9]+")) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri, new String[] {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA
}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
final String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
// TODO: apply filter on the file name to ensure it's screen shot event
Log.d("file", "FILE CHANGE OCCURED " + fileName + " " + path);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
super.onChange(selfChange, uri);
}
}
);
}
}
And run time permissions as:
private void getPermission(){
boolean hasPermission = (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
if (!hasPermission) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_READ_STORAGE);
}
}
And received that permissions result in onRequestPermissionsResult.
This method didn't work for me. So I tried with FileObserver inside that service. That time also it works in all other platforms, but not Marshmallow.
This appears to be a bug in Marshmallow, see here.
You can only try working around it by polling for whatever information you need.
How well this will work for you depends on your use case. I found it usable for tracking download progress: start polling when the download starts, with a one-second interval, and stop when the download finishes.
If you expect very infrequent changes, you can try increasing the interval – at the cost of a potentially higher delay between changes happening and your app picking them up.

contentobserver works only for insert and delete but not for an update

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.

SMS sent observer executes 3 times

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.

Intercepting Outgoing SMS

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" />

Why is the ContentObserver called multiple times?

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.
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.
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();
}
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.

Categories

Resources