I've spent 5 days trying out different things and lots of googling with no luck
I have a broadcast receiver to monitor and backup incoming mms & sms.
The sms - outgoing and incoming is easy no problem. MMS however...
I have a broadcast receiver for incoming MMS, no problem there.
For outgoing MMS however, I use a content observer directed towards content://mms
heres the part registering the content observer from the service class
mo = new MMSObserver(new Handler(),getApplicationContext());
try{
getContentResolver().unregisterContentObserver(mo);
}
finally{
getContentResolver().registerContentObserver(Uri.parse("content://mms"), true, mo);
}
This is the onchange part in the above content observer
public void onChange(boolean bSelfChange)
{
super.onChange(bSelfChange);
Log.i(TAG,"MMSObserver onChange");
ContentResolver contentResolver = context.getContentResolver();
Uri uri = Uri.parse("content://mms");
Cursor cur = contentResolver.quert("content://mms",null,null,null,null)
if(cur.moveToNext()){
String id = cur.getString(cur.getColumnIndex("_id"));
String date = cur.getString (cur.getColumnIndex ("date"));
String address = getAddress(id);
}
}
private static String getAddress(String id){
String selectionAdd = new String("msg_id=" + id);
String uriStr = MessageFormat.format("content://mms/{0}/addr", id);
Uri uriAddress = Uri.parse(uriStr);
Cursor cAdd = context.getContentResolver().query(uriAddress, null,
selectionAdd, null, null);
String name = null;
if (cAdd.moveToFirst()) {
do {
String number = cAdd.getString(cAdd.getColumnIndex("address"));
if (number != null) {
try {
Long.parseLong(number.replace("-", ""));
name = number;
} catch (NumberFormatException nfe) {
if (name == null) {
name = number;
}
}
}
} while (cAdd.moveToNext());
}
if (cAdd != null) {
cAdd.close();
}
return name;
}
The problem is the address column always returns "insert-address-token" for outgoing mms.
Is there any possible way to get the number the mms is going to?
Also I noticed that the content observer is triggered when the message is in draft form not when it is sent or pending. since depending on those uris is generally a bad idea since they're not part of the sdk, i switched to a different method. cataloging all sms & mms messages and storing their _id columns, and just syncing them with the backup. However my problem still remains.
MMS address column is always "insert-address-token"
Any suggestions?
You may want to try something like this:
Uri uri = Uri.parse("content://mms-sms/conversations/" + mThreadId);
String[] projection = new String[] {
"body", "person", "sub", "subject", "retr_st", "type", "date", "ct_cls", "sub_cs", "_id", "read", "ct_l", "st", "msg_box", "reply_path_present", "m_cls", "read_status", "ct_t", "status", "retr_txt_cs", "d_rpt", "error_code", "m_id", "date_sent", "m_type", "v", "exp", "pri", "service_center", "address", "rr", "rpt_a", "resp_txt", "locked", "resp_st", "m_size"
};
String sortOrder = "normalized_date";
Cursor mCursor = getActivity().getContentResolver().query(uri, projection, null, null, sortOrder);
String messageAddress;
int type;
while (mCursor.moveToNext()) {
String messageId = mCursor.getString(mCursor.getColumnIndex("_id"));
Uri.Builder builder = Uri.parse("content://mms").buildUpon();
builder.appendPath(messageId).appendPath("addr");
Cursor c = mContext.getContentResolver().query(builder.build(), new String[] {
"*"
}, null, null, null);
while (c.moveToNext()) {
messageAddress = c.getString(c.getColumnIndex("address"));
if (!messageAddress.equals("insert-address-token")) {
type = c.getInt(c.getColumnIndex("type"));
c.moveToLast();
}
}
c.close();
}
I am not actually calling the mCursor's moveToNext() method in my code but instead I am implementing this logic in the getView() method of a SimpleCursorAdapter.
Related
I'm looking for the count of sent sms messages in a particular thread (say, with id 15). I found this How do I get the count of SMS messages per contact into a textview? <-- but it doesn't solve my issue, as it counts both sent and received sms messages. Is it possible to count only sent messages? I think I could query "content://sms/sent" and walk through each SMS, but I wonder if there's a more efficient way.
Thanks
You can query Sms.Conversations with your thread ID, and a selection that restricts the TYPE column to MESSAGE_TYPE_SENT. Since you just want the count, we can do a SELECT COUNT() query, so there's no resources wasted building a Cursor with unused values. For example:
private int getThreadSentCount(String threadId) {
final Uri uri = Sms.Conversations.CONTENT_URI
.buildUpon()
.appendEncodedPath(threadId)
.build();
final String[] projection = {"COUNT(1)"};
final String selection = Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT;
int count = -1;
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri,
projection,
selection,
null,
null);
if (cursor != null && cursor.moveToFirst()) {
count = cursor.getInt(0);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (cursor != null) {
cursor.close();
}
}
return count;
}
The Sms class used above is in the android.provider.Telephony class.
import android.provider.Telephony.Sms;
For reference, Sms.Conversations.CONTENT_URI is equivalent to Uri.parse("content://sms/conversations"), Sms.TYPE is "type", and Sms.MESSAGE_TYPE_SENT is 2.
My application uses the Contacts ContentProvider to store some of its data. When I load a contact into memory, I want to save its ID (so that I know how to save changes later), and the ID of all data fields it is using (so they can be directly updated). Here is some of my code:
Uri entityUri = Uri.withAppendedPath(
ContentUris.withAppendedId(RawContacts.CONTENT_URI, id),
Entity.CONTENT_DIRECTORY);
Cursor resultData = context.getContentResolver().query(
entityUri,
new String[]{RawContacts.SOURCE_ID, Entity.DATA_ID, Entity.MIMETYPE, Entity.DATA1},
null, null, null);
resultData.moveToFirst();
this.id = id;
while (resultData.isAfterLast() == false) {
this.source_id = resultData.getInt(0);
if (!resultData.isNull(1)) {
if (resultData.getString(2).equals(Fields.DISPLAY_NAME)) {
this.display_name = resultData.getString(3);
this.display_name_id = resultData.getInt(1);
}
}
resultData.moveToNext();
}
resultData.close();
return this;
That queries the ContentProvider and gets the DISPLAY_NAME field from the data. The ID of the data record is stored in the display_name_id variable. It comes out as 4612 when I run it on my device.
I tried saving it, but it does not update as expected. In order to debug, I added a query that tries to find the correct data table record.
Cursor c = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
new String[] {ContactsContract.Data._ID, CommonDataKinds.StructuredName.DISPLAY_NAME},
ContactsContract.Data._ID + "=?",
new String[] {String.valueOf(this.display_name_id)}, null);
However, this cursor comes back as having a length of 0. How can this be? Why is the ID incorrect?
I tried locally and it works for me, here's my code slightly adapted from yours:
public void testContacts(final #Nonnull Context context, final int rawContactId, final #Nonnull String expectedDisplayName) {
Uri entityUri = Uri.withAppendedPath(
ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId),
ContactsContract.RawContacts.Entity.CONTENT_DIRECTORY);
Cursor resultData = context.getContentResolver().query(
entityUri,
new String[]{
ContactsContract.RawContacts.SOURCE_ID,
ContactsContract.RawContacts.Entity.DATA_ID,
ContactsContract.RawContacts.Entity.MIMETYPE,
ContactsContract.RawContacts.Entity.DATA1
},
null, null, null);
int displayNameId = -1;
try {
final int columnIndexDataId = resultData.getColumnIndex(ContactsContract.RawContacts.Entity.DATA_ID);
final int columnIndexMimetype = resultData.getColumnIndex(ContactsContract.RawContacts.Entity.MIMETYPE);
final int columnIndexData = resultData.getColumnIndex(ContactsContract.RawContacts.Entity.DATA1);
while (resultData.moveToNext()) {
if (!resultData.isNull(columnIndexDataId)) {
if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(resultData.getString(columnIndexMimetype)) &&
expectedDisplayName.equals(resultData.getString(columnIndexData))) {
displayNameId = resultData.getInt(1);
break;
}
}
}
} finally {
resultData.close();
}
String reLookedUpDisplayName = null;
if (displayNameId != -1) {
Cursor reLookupCursor = context.getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
new String[] {
ContactsContract.Data._ID,
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
},
ContactsContract.Data._ID + "=?",
new String[] {String.valueOf(displayNameId)},
null);
try {
final int columnIndexId = reLookupCursor.getColumnIndex(ContactsContract.Data._ID);
final int columnIndexDisplayName = reLookupCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
while (reLookupCursor.moveToNext()) {
reLookedUpDisplayName = reLookupCursor.getString(columnIndexDisplayName);
}
} finally {
reLookupCursor.close();
}
}
Toast.makeText(
context,
reLookedUpDisplayName != null ? "Found re-looked up name: " + reLookedUpDisplayName : "Didn't find name re-looking it up",
Toast.LENGTH_LONG)
.show();
}
There's no big difference from your code, so compare or try to replace bits of it to see where you have a problem. Make sure you use a fresh Cursor for each query, and close it correctly afterwards (in a finally clause).
Another thing, make sure that if (resultData.getString(2).equals(Fields.DISPLAY_NAME)) is really what you're wanting to do (it compares the entry mime type with Fields.DISPLAY_NAME), but since you're saying you get the data ID correctly this shouldn't be the problem.
my app creates new contacts with ContentProviderOperation. The problem is, I need a reference to the new contact because I need some information of it to display it in a listview and go with intent into the contact app to the contact.
The best thing would be the ID, but I´ve read that it might change during operations on the database, which won´t be helpful to me.
Now I thought, the Uri might be the best thing, because I could later retrieve the contactID or lookup key.
How do I get the Uri directly after calling applyBatch() ?
EDIT:
Here is a solution, but not really a good one.
He is putting a randomly generated token into each contact, then he makes a new query with it.
I want neither put some extra data into the contacts, nor starting a second query. But if there is no other possibility I´ll do it that way.
simply call
private String retrieveContactId(String phoneNo) {
try {
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNo));
String[] projection = new String[] { ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME };
String selection = null;
String[] selectionArgs = null;
String sortOrder = ContactsContract.PhoneLookup.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
ContentResolver cr = getApplicationContext().getApplicationContext().getContentResolver();
String contactId = null;
if (cr != null) {
Cursor resultCur = cr.query(uri, projection, selection, selectionArgs, sortOrder);
if (resultCur != null) {
while (resultCur.moveToNext()) {
contactId = resultCur.getString(resultCur.getColumnIndex(ContactsContract.PhoneLookup._ID));
Log.e("Info Incoming", "Contact Id : " + contactId);
return contactId;
}
resultCur.close();
}
}
} catch (Exception sfg) {
Log.e("Error", "Error in loadContactRecord : " + sfg.toString());
}
return null;
}
and for the uri
Uri contactUri = Contacts.getLookupUri(
Integer.valueOf(rawContactId), clookup);
I am using the following code to insert a draft into content://sms/draft
ContentValues values = new ContentValues();
values.put("address", receiver2);
values.put("body", body2);
values.put("date", String.valueOf(System.currentTimeMillis()));
values.put("type", "3");
values.put("thread_id", thread_id);
getContentResolver().insert(Uri.parse("content://sms/draft"), values);
thread_id is 0 if there wasn't any conversation with the address above, else it's the id of that thread.
When I run this code, the draft is indeed saved, but thread in the native sms client (stock android 4.0.3) isn't updated as "draft" [I can see the draft message body, but there is no "Draft" label on it. I have to open-close the thread, in order to be marked as marked]. I have read somewhere that there is an issue with the thread not updating properly. How can I force the threads to be updated so it shows ok in all the clients?
EDIT:
Having read your answers, I have updated my code a bit, but the problem remains. I have added a screenshot below, since when I wrote my question I was in a hurry and couldn't write it clearly enough.
protected void save_draft(String[] recipients, String body) {
Uri threadIdUri = Uri.parse("content://mms-sms/threadID");
Uri.Builder builder = threadIdUri.buildUpon();
for (String recipient : recipients) {
builder.appendQueryParameter("recipient", recipient);
}
Uri uri = builder.build();
Long thread_id = get_thread_id(uri);
Log.d("thread_id", thread_id + " ");
ContentValues values = new ContentValues();
values.put("body", body);
values.put("date", String.valueOf(System.currentTimeMillis()));
values.put("type", 3);
values.put("thread_id", thread_id);
getContentResolver().insert(Uri.parse("content://sms/draft"), values);
//^tried "content://sms/" as well, but got the same result
}
private Long get_thread_id(Uri uri) {
long threadId = 0;
Cursor cursor = getContentResolver().query(uri, new String[] { "_id" },
null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
threadId = cursor.getLong(0);
}
} finally {
cursor.close();
}
}
return threadId;
}
As you can see, there is no "Draft" label, next to the draft I made via the code above.
It's been a while since I made this question, but here is the answer:
First of all as stated before, the fact that the "Draft" hint doesn't appear on the Native SMS app, shouldn't be bothering anyone. Nothing can be done about it, and it's just the way the Native SMS app works. In particular a cache is initialised when the app starts, saving the thread ids of the threads that contain a draft. The draft cache is updated only from the app itself and not from an actual change in the sms table
For the saving draft part here is the piece of code to save a draft properly:
public static final Uri CONTENT_URI =
Uri.parse("content://sms/draft");
public static Uri addDraft(ContentResolver resolver,
String address, String body, String subject,
Long date, long threadId) {
ContentValues values = new ContentValues(6);
values.put(ADDRESS, address);
if (date != null) {
values.put(DATE, date);
}
values.put(READ, Integer.valueOf(1));
values.put(SUBJECT, subject);
values.put(BODY, body);
if (threadId != -1L) {
values.put(THREAD_ID, threadId);
}
return resolver.insert(CONTENT_URI , values);
}
Note: Draft messages may or may not contain the address of the recipient of the message. Drafts are saved on the thread (a thread can contain many recipients)
Although the sms database is not documented at all, you can grab the Telephony class from the AOSP and have a look at how to add/remove messages and handle various tasks about sms and mms.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2.2_r1/android/provider/Telephony.java
I think your approach is different from built-in messaging application.
thread_id is 0 if there wasn't any conversation with the address above, else it's the id of that thread.
As far as i know even draft got auto generated thread_id. If it's not, all draft (had receiver address never appeared) will group at the same conversation with thread_id = 0
Here is how built-in app add an draft.
public static final Uri SmsCONTENT_URI =
Uri.parse("content://sms");
ContentValues values = new ContentValues(3);
values.put("thread_id", threadId);
values.put("body", contents); //
values.put("type", Sms.MESSAGE_TYPE_DRAFT); // type = 3 is draft.
SqliteWrapper.insert(mActivity, mContentResolver, Sms.CONTENT_URI, values);
Final reminder : This is not public API to access message data so I don't suggest you to use. But now is the only way.
Use the method described in this answer , just insert to content://sms/draft instead of content://sms/sent.
thanks a lot i have try save_draft() try this and insert into inbox/sent/draft etc....
public class AddData {
Activity act;
Context ctx,context;
ContentResolver cr;
public AddData(Activity act)
{
cr = act.getContentResolver();
this.act = act;
}
public void addsms(String address,String body,String date,String type,String read)
{
String[] addr = address.split(" ");
String thread_id = save_draft(addr);
ContentValues values = new ContentValues();
values.put("body", body);
values.put("date", date);
values.put("type", type);
if(type.equals("3"))
{
values.put("thread_id", thread_id);
}else
{
values.put("address", address);
}
Uri uri = cr.insert(Uri.parse("content://sms/"), values);
cr.notifyChange(uri, null);
}
protected String save_draft(String[] recipients) {
Uri threadIdUri = Uri.parse("content://mms-sms/threadID");
Uri.Builder builder = threadIdUri.buildUpon();
for (String recipient : recipients) {
builder.appendQueryParameter("recipient", recipient);
}
Uri uri = builder.build();
String thread_id = get_thread_id(uri).toString();
Log.d("thread_id", thread_id + " ");
//^tried "content://sms/" as well, but got the same result
return thread_id;
}
private Long get_thread_id(Uri uri) {
long threadId = 0;
Cursor cursor = act.getContentResolver().query(uri, new String[] { "_id" },
null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
threadId = cursor.getLong(0);
}
} finally {
cursor.close();
}
}
return threadId;
}
}
I want to delete some certain SMS automatically in my Android application. Therefore I have a method which does exactly what I want it to do. However, it only works if I deploy the application directly to my phone from Eclipse. Then it deletes incoming SMS. However, it does not work if the application is downloaded from the market. But there is also no error. Does anybody know how I can solve this or does this only work on rooted devices?
public void deleteSMS(Context context, String message, String number) {
try {
mLogger.logInfo("Deleting SMS from inbox");
Uri uriSms = Uri.parse("content://sms/inbox");
Cursor c = context.getContentResolver().query(uriSms,
new String[] { "_id", "thread_id", "address",
"person", "date", "body" }, null, null, null);
if (c != null && c.moveToFirst()) {
do {
long id = c.getLong(0);
long threadId = c.getLong(1);
String address = c.getString(2);
String body = c.getString(5);
if (message.equals(body) && address.equals(number)) {
mLogger.logInfo("Deleting SMS with id: " + threadId);
context.getContentResolver().delete(
Uri.parse("content://sms/" + id), null, null);
}
} while (c.moveToNext());
}
} catch (Exception e) {
mLogger.logError("Could not delete SMS from inbox: " + e.getMessage());
}
}
Actually, the code in my post is 100% correct. The problem was that Android needs some time to store the SMS upon receiving it. So the solution is to just add a handler and delay the delete request for 1 or 2 seconds.
This actually solved the whole issue.
EDIT (thanks to Maksim Dmitriev):
Please consider that you can't delete SMS messages on devices with Android 4.4.
Also, the system now allows only the default app to write message data to the provider, although other apps can read at any time.
http://developer.android.com/about/versions/kitkat.html
No exception will be thrown if you try; nothing will be deleted. I have just tested it on two emulators.
How to send SMS messages programmatically
Please consider that you can't delete SMS messages on devices with Android 4.4.
Also, the system now allows only the default app to write message data
to the provider, although other apps can read at any time.
http://developer.android.com/about/versions/kitkat.html
No exception will be thrown if you try; nothing will be deleted. I have just tested it on two emulators.
How to send SMS messages programmatically
hey use this code to delete customize sms
1. By date
2. By number
3. By body
try {
Uri uriSms = Uri.parse("content://sms/inbox");
Cursor c = context.getContentResolver().query(
uriSms,
new String[] { "_id", "thread_id", "address", "person",
"date", "body" }, "read=0", null, null);
if (c != null && c.moveToFirst()) {
do {
long id = c.getLong(0);
long threadId = c.getLong(1);
String address = c.getString(2);
String body = c.getString(5);
String date = c.getString(3);
Log.e("log>>>",
"0--->" + c.getString(0) + "1---->" + c.getString(1)
+ "2---->" + c.getString(2) + "3--->"
+ c.getString(3) + "4----->" + c.getString(4)
+ "5---->" + c.getString(5));
Log.e("log>>>", "date" + c.getString(0));
ContentValues values = new ContentValues();
values.put("read", true);
getContentResolver().update(Uri.parse("content://sms/"),
values, "_id=" + id, null);
if (message.equals(body) && address.equals(number)) {
// mLogger.logInfo("Deleting SMS with id: " + threadId);
context.getContentResolver().delete(
Uri.parse("content://sms/" + id), "date=?",
new String[] { c.getString(4) });
Log.e("log>>>", "Delete success.........");
}
} while (c.moveToNext());
}
} catch (Exception e) {
Log.e("log>>>", e.toString());
}
You can choose which app is the default SMS app in 4.4+ and if your app is set as the default it will be able to delete SMS as well.
to make app as default app see this.
Check if your app is default sms app before deleting.
Use the URI provided by telephony class instead of hardcoding.
public void deleteSMS(Context context,int position)
{
Uri deleteUri = Uri.parse(Telephony.Sms.CONTENT_URI);
int count = 0;
Cursor c = context.getContentResolver().query(deleteUri, new String[]{BaseColumns._ID}, null,
null, null); // only query _ID and not everything
try {
while (c.moveToNext()) {
// Delete the SMS
String pid = c.getString(Telephony.Sms._ID); // Get _id;
String uri = Telephony.Sms.CONTENT_URI.buildUpon().appendPath(pid)
count = context.getContentResolver().delete(uri,
null, null);
}
} catch (Exception e) {
}finally{
if(c!=null) c.close() // don't forget to close the cursor
}
}
it delete all(inbox,outbox,draft) the SMS.
private int deleteMessage(Context context, SmsMessage msg) {
Uri deleteUri = Uri.parse("content://sms");
int count = 0;
#SuppressLint("Recycle") Cursor c = context.getContentResolver().query(deleteUri, null, null, null, null);
while (c.moveToNext()) {
try {
// Delete the SMS
String pid = c.getString(0); // Get id;
String uri = "content://sms/" + pid;
count = context.getContentResolver().delete(Uri.parse(uri),
null, null);
} catch (Exception e) {
}
}
return count;
}
use this code.............
or
getContentResolver().delete(Uri.parse("content://sms/conversations/" + threadIdIn), null, null);
I was looking for a method to delete all SMS with one click. Thanks to this post I succeeded.
Here is my method if it interests someone :
private void deleteSMS(){
Uri myUri= Uri.parse("content://sms/");
Cursor cursor = getContext().getContentResolver().query(myUri, null,null,null,null);
while (cursor.moveToNext()) {
int thread_id = cursor.getInt(1);
getContext().getContentResolver().delete(Uri.parse("content://sms/conversations/" + thread_id),null,null);
}
cursor.close();
}
if you want get a message and your sms app your android device phone didn't send any notification use Binary (Data) SMS.