I'm trying to make an SMS observer for outgoing messages using the ContentObserver. The code below works perfectly, but whenever I test this by sending a text message to a colleague, I get the output twice.
The Observer gets registered in a Service, like this:
SentSMSObserver sentSMSObserver = new SentSMSObserver(new Handler(), this);
getContentResolver().registerContentObserver(sentSMSObserver.CONTENT_SMS_URI, true, sentSMSObserver);
Whenever I send a textmessage to my own number, I only get the output once, which is really weird. As a Service is a Singleton (as far as my research has led me to believe), it's quite improbable there is a second instance of my observer.
public class SentSMSObserver extends ContentObserver {
private static final String CONTENT_SMS = "content://sms";
public final Uri CONTENT_SMS_URI = Uri.parse(CONTENT_SMS);
private Context context;
public SentSMSObserver(Handler handler, Context context) {
super(handler);
this.context = context;
}
#Override
public void onChange(boolean selfChange) {
Cursor cursor = context.getContentResolver().query(CONTENT_SMS_URI, null, null, null, null);
try {
if (cursor.moveToNext()) {
String protocol = cursor.getString(cursor.getColumnIndex("protocol"));
int type = cursor.getInt(cursor.getColumnIndex("type"));
if (protocol != null || type != Telephony.TextBasedSmsColumns.MESSAGE_TYPE_SENT) {
return;
}
String to = cursor.getString(cursor.getColumnIndex("address"));
Date now = new Date(cursor.getLong(cursor.getColumnIndex("date")));
String message = cursor.getString(cursor.getColumnIndex("body"));
Log.e("sentmessage", to + " - " + now + " - " + message);
}
} finally {
cursor.close();
}
}
}
Logcat:
08-11 14:25:14.292 12574-12574/com.androidfun.smstest E/sentmessage﹕ +(deleted phone n°) - Mon Aug 11 14:25:10 CEST 2014 - Test
08-11 14:25:18.306 12574-12574/com.androidfun.smstest E/sentmessage﹕ +(deleted phone n°) - Mon Aug 11 14:25:10 CEST 2014 - Test
Sent messages are also updated with "sent confirmation" and even "delivery confirmation" in some cases. Sending it to yourself, you probably are not getting a "sent confirmation" because it sends and confirms in one step.
See the post here:
SMS sent observer executes 3 times
Related
I have setup a background service to registerContentObserver to get notified whenever an SMS is sent. Upon receiving this event, I would increment a variable to know the count of messages sent. This is working as expected.
When someone sends SMS with more than 140 characters, the mobile carrier would treat this as multiple SMS, but it seems that I get only 1 callback for the sent message. This is causing my app to miss counting some messages.
Is there any proper way to know how many messages were actually sent?
When an app is responsible for writing its own messages to the Provider, it's most likely going to write the whole message in one go, regardless of whether the message must be sent as multipart. This would be why your Observer is often firing only once for each complete message, no matter how big.
Since KitKat, the system will automatically save the outgoing messages for any non-default apps, and for multipart messages, each part will be saved individually, firing your Observer each time. Of course, this doesn't help for anything prior to KitKat, or if a default app saves its own messages on later versions.
One possibility is to fetch the message body in your ContentObserver, and determine how many message parts it would've been split into. The SmsMessage.calculateLength() method can do this for us. It returns an int array, the first element of which will have the message count for the given text.
For example, using the old onChange(boolean) method, to support API < 16:
private class SmsObserver extends ContentObserver {
private static final Uri SMS_SENT_URI = Uri.parse("content://sms/sent");
private static final String COLUMN_ID = "_id";
private static final String COLUMN_BODY = "body";
private static final String[] PROJECTION = {COLUMN_ID, COLUMN_BODY};
// You might want to persist this value to storage, rather than
// keeping a field, in case the Observer is killed and recreated.
private int lastId;
public SmsObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange) {
Cursor c = null;
try {
// Get the most recent sent message.
c = getContentResolver().query(SMS_SENT_URI, PROJECTION, null,
null, "date DESC LIMIT 1");
if (c != null && c.moveToFirst()) {
// Check that we've not already counted this one.
final int id = c.getInt(c.getColumnIndex(COLUMN_ID));
if (id == lastId) {
return;
}
lastId = id;
// Get the message body, and have the SmsMessage
// class calculate how many parts it would need.
final String body = c.getString(c.getColumnIndex(COLUMN_BODY));
final int numParts = SmsMessage.calculateLength(body, false)[0];
// Add the number of parts to the count,
// however you might be doing that.
addToCount(numParts);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (c != null) {
c.close();
}
}
}
}
Should you be supporting API 16 and above, we can use the onChange(boolean, Uri) overload, and things get a little simpler, since we don't necessarily need to keep track of the last message ID.
#Override
public void onChange(boolean selfChange, Uri uri) {
Cursor c = null;
try {
// type=2 restricts the query to the sent box, so this just
// won't return any records if the Uri isn't for a sent message.
c = getContentResolver().query(uri, PROJECTION, "type=2", null, null);
if (c != null && c.moveToFirst()) {
final String body = c.getString(c.getColumnIndex(COLUMN_BODY));
final int numParts = SmsMessage.calculateLength(body, false)[0];
addToCount(numParts);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (c != null) {
c.close();
}
}
}
I am trying to format a phone number to E164 , in vain :
//Detects outgoing call :
private class MyOutCallsReceiver extends BroadcastReceiver{
public MyOutCallsReceiver() {super();}
#Override
public void onReceive(Context context, Intent intent) {
String outgoingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.v("OUTCALL-outgoing",outgoingNumber);
String phone = MyApplication.TryFormatPhoneNumberToE164(outgoingNumber);
Log.v("OUTCALL-phone",phone);
}
Logcat:
11-11 14:23:19.836 495-495/ma.altaiir.app V/OUTCALL-outgoing:
0612345678
11-11 14:23:19.841 495-495/ma.altaiir.app V/OUTCALL-phone:
0612345678
Formatting method :
public static String TryFormatPhoneNumberToE164(String phone){
String result = PhoneNumberUtils.formatNumberToE164(phone,_CountryIsoCode);
if(result == null){result = PhoneNumberUtils.formatNumberToE164(PhoneNumberUtils.normalizeNumber(phone),_CountryIsoCode);}
Log.v("COUNTRYISOCODE/NUMB",_CountryIsoCode + "/" + phone + "/" + result);
if(result == null){return phone;}else{ return result;}
}
Logcat:
11-11 14:23:19.841 495-495/ma.altaiir.app V/COUNTRYISOCODE/NUMB:
ma/0612345678/null
All this means , the method is just returning null whatever I do to avoid this, is this known drawback or it is something I do wrong ?
Finally i ended up using libphonenumber wich resolved all my number parsing problems, but still wondering why builin method do not work !
Anyways, for any one facing the sale parsing problems, try https://github.com/googlei18n/libphonenumber
In my application i want to get sms/mms from device and display the messages in listview.By using the following code to get the all sms from device.
public void readSmsFromDevice() {
preferences = PreferenceManager
.getDefaultSharedPreferences(BackgroundService.this);
final_msg_time = preferences.getLong("msgtime", 0);
Uri uriSMSURI = Uri.parse("content://sms/");
String[] projection = { "address", "body", "date", "type" };
String where = "date" + ">" + final_msg_time;
Cursor cur = getContentResolver().query(uriSMSURI, projection, where,null, "date");
while (cur.moveToNext()) {
if(ProfileFragment.stop)
{
break;
}else{
try {
//
Message mess1=new Message();
try{
String _id = cur.getString(cur.getColumnIndex("_id"));
mess1.setId(_id);
}catch(Exception e)
{
mess1.setId("null");
}
try{
String number = cur.getString(cur.getColumnIndex("address"));
number = number.replaceAll("[\\W]", "");
if (number.trim().length() > 10) {
mess1.setNumber(number.substring(number.length() - 10,
number.length()));
mess1.setAddress(number.substring(number.length() - 10,
number.length()));
} else {
mess1.setNumber(number);
mess1.setAddress(number);
}
}
catch(Exception e){}
mess1.setBody(cur.getString(cur.getColumnIndex("body")));
String type = cur.getString(cur.getColumnIndex("type"));
Long millisecond = Long.parseLong(cur.getString(cur
.getColumnIndex("date")));
String dateString = DateFormat.format("yyyy/MM/dd hh:mm:ss a",
new Date(millisecond)).toString();
mess1.setDate_millis(millisecond);
mess1.setDate(dateString);
mess1.setType(type);
mess1.setmessagetype("sms");
messages.add(mess1);
} catch (Exception e) {}
}
}
cur.close();
}
By using this method i am getting all sms from device.But my question is how to differenciate group message.In group message one message sent different contact numbers(senders).So in normal message application group message displayed in separate column and single message displayed in separate column.So my application also i have to display the messages like message application.So in this cursor how to identify group message?Is there any column is available to identify group message?So please suggest me how to do taht.Thanks In Advance.....
the thread THREAD_ID column will give you all the messages in the same conversation. You can then use the address column to differentiate the senders of the messages in the group message.
You should also use the Telephony class that was introduced in API 19 instead of the content resolver. https://developer.android.com/reference/android/provider/Telephony.html
sir, i have a problem in updating my database. i've made a listview containing this data
name
phone number
status
i would like to update the status of person if he sent a message with a keyword "available" in it and would update my listview like this
name
phone number
available
so i decided to make the phone number as the trigger. if the phone number of the person who sent the message is in my listview, it will update the database. but here is my problem, if i saved the phone number in my listview in this format
09211234567
the sender will return their phone number as
+639211234567
so i worked around this by getting the substring and cut the phone number into "9211234567", and then add 0 to transform it into "09211234567".
however, the database status still doesn't update. but when i used the same technique in sending sms from emulator to emulator, it works just fine.
i saved the number of emulator in my listview as
5556
but the emulator returns
15555215556
so i just get the substring to get 5556
please help me. here is my code:
public static String sender;
public GroupDb info;
public String aStatus = "available";
public String nStatus = "not available";
public String addNum = "0";
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
info = new GroupDb(context);
Bundle bundle = intent.getExtras();
Object[] pdusObj = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[pdusObj.length];
for (int i = 0; i<pdusObj.length; i++)
{
messages[i] = SmsMessage.createFromPdu ((byte[])
pdusObj[i]);
sender = messages[i].getOriginatingAddress();
}
for (SmsMessage msg : messages) {
if (msg.getMessageBody().contains("available")) {
info.open();
String remFirstChar = sender.substring(3);
addNum += remFirstChar;
Toast.makeText(context.getApplicationContext(), "received sms from: " +addNum,
Toast.LENGTH_LONG).show();
//if starts with +639
if(sender.length() == 13)
{
info.updateStatus(addNum, aStatus);
Toast.makeText(context.getApplicationContext(), "addNum: " +addNum,
Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(context.getApplicationContext(), "sender: " +sender,
Toast.LENGTH_LONG).show();
info.updateStatus(remFirstChar, aStatus);
}
info.close();
}//end if - available
here is how i updated my status
//update status
public void updateStatus(String mNumber, String mStatus) throws SQLException
{
// TODO Auto-generated method stub
ContentValues cvUpdate = new ContentValues();
cvUpdate.put(KEY_STATUS, mStatus);
ourDatabase.update(DATABASE_TABLE, cvUpdate, KEY_NUMBER + "=" + mNumber, null);
}
update:
i even tried to enter "+63" format in listview but still, i won't update. all functions such as deletion and editing also don't work and shows force close.
You need to use PhoneNumberUtils class.
Before saving the Phone Number in database first convert it as
String formattedNumber = PhoneNumberUtils.formatNumber(phonenumber);
For comparison you can use PhoneNumberUtils.compare
is there anyway to remove a missed call notification by code? And somehow remove the last missed call from call history?
yes, it is possible.Try this:
Uri UriCalls = Uri.parse("content://call_log/calls");
Cursor cursor = getApplicationContext().getContentResolver().query(UriCalls, null, null, null, null);
Reading call log entries...
if(cursor.getCount() > 0){
cursor.moveToFirst();
while(!cursor.isAfterLast()){
String number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)); // for number
String name = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME));// for name
String duration = cursor.getString(cursor.getColumnIndex(CallLog.Calls.DURATION));// for duration
int type = Integer.parseInt(cursor.getString(cursor.getColumnIndex(CallLog.Calls.TYPE)));// for call type, Incoming or out going
cursor.moveToNext();
}
}
Deleting entry in call log...
String queryString= "NUMBER='" + number + "'";
if (cursor.getCount() > 0){
getApplicationContext().getContentResolver().delete(UriCalls, queryString, null);
}
Permission:
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
Note: Please refer this doc over call log for more clearity.
Using the above code you can get the desired result.
Refer to this
You can clear your missed call by calling cancel(ID) or calcelAll() to clear your notifications bar.
For the part where you want to remove the last call from the log you need to move the method that is deleting the entry into a class which is a subclass of the Thread class. This allows you to then set it to sleep for a short period to allow Android to actually write to the call log BEFORE you you run the delete query. I had the same problem, but manage to resolve it with the code below:
public class DelayClearCallLog extends Thread {
public Context context;
public String phoneNumber;
public DelayClearCallLog(Context ctx, String pNumber){
context = ctx;
phoneNumber = pNumber;
}
public void run() {
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clearCallLog(context, phoneNumber);
}
public void clearCallLog(Context context, String phoneNumber) {
// implement delete query here
}
}
Then call the the method as follows:
DelayClearCallLog DelayClear = new DelayClearCallLog(context, phoneNumber);
DelayClear.start();