Recently, I have been working on an Android project that requires me to get the text from and MMS. I don't have a whole lot of experience with Android, but I am familiar with SQL and java and I have been following these posts as closely as possible.
Detecting MMS messages on Android
How to Read MMS Data in Android?
The code that I came up with mostly works, but it seems to always grab the text from the second most recent MMS message and not the most recent one. Any idea why this would be happening?
Here are the relevant parts of the MMSReceiver class that I have made using the two threads mentioned earlier as a guide.
First, is the onReceive method...
public class MMSReceiver extends BroadcastReceiver {
private static final String ACTION_MMS_RECEIVED = "android.provider.Telephony.WAP_PUSH_RECEIVED";
private static final String MMS_DATA_TYPE = "application/vnd.wap.mms-message";
private static String message;
#Override
public void onReceive(Context context, Intent intent) {
Log.d("Test" , "MMSReceiver!");
Uri uri = Uri.parse("content://mms-sms/conversations");
String action = intent.getAction();
String type = intent.getType();
//Log.d("MMS", "action is " + action + " , type is " + type);
final Context cont = context;
ContentResolver cr = context.getContentResolver();
final String[] projection = new String[]{"*"};
Uri MMSuri = Uri.parse("content://mms/inbox/");
Cursor MmsIDquery = cr.query(uri, projection, null, null, null);
MmsIDquery.moveToFirst();
String mid = MmsIDquery.getString(MmsIDquery.getColumnIndex("_id"));
Log.d("MMS", "message id for unreadable message is " + mid);
message = getMessageText(cont, mid);
...
The getMessageText method is passed the context and the message id and is written as this...
/* gets message text */
public String getMessageText(Context context, String mmsid){
Log.d("MMS", "mmsid was " + mmsid);
String message = null;
String mid = ""+(Integer.parseInt(mmsid)+2);
Log.d("MMS", "mmsid is now " + mid);
String selectionPart = "mid=" + mmsid;
Uri mmsTextUri = Uri.parse("content://mms/part");
Cursor cursor = context.getContentResolver().query(mmsTextUri, null, selectionPart, null, null);
if (cursor.moveToFirst()){
do{
String partId = cursor.getString(cursor.getColumnIndex("_id"));
String type = cursor.getString(cursor.getColumnIndex("ct"));
Log.d("MMS", "getMessageText was called, partId = " + partId + " , type = " + type);
if ("text/plain".equals(type)){
String data = cursor.getString(cursor.getColumnIndex("_data"));
Log.d("MMS", "data was " + data);
if (data != null){
message = getMmsText(context, partId);
Log.d("MMS", "body was " + message);
} else {
message = cursor.getString(cursor.getColumnIndex("text"));
Log.d("MMS", "body was " + message);
}
}
} while (cursor.moveToNext());
} else {
Log.d("MMS", "Query returned nothing in getMessageText()");
}
return message;
}
Within this method, the getMmsText is called. I realize that this process is a bit redundant, but I was having a hard time understanding all of what goes on during this process so I kept it as similar to the original threads as I could in order to be sure that the problem wasn't in the way that I reduced it for my own code.
getMmsText looks like this...
public String getMmsText(Context c, String id){
Log.d("MMS", "getMmsText was called with " + id);
Uri partUri = Uri.parse("content://mms/inbox/" + id);
InputStream is = null;
StringBuilder sb = new StringBuilder();
try{
is = c.getContentResolver().openInputStream(partUri);
if (is != null){
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader reader = new BufferedReader(isr);
String temp = reader.readLine();
while(temp != null){
sb.append(temp);
temp = reader.readLine();
}
}
} catch (IOException e){}
finally {
if (is != null){
try{
is.close();
} catch (IOException e){}
}
}
return sb.toString();
}
Thank you so much for your help and please let me know if you have any questions that can help with your answering.
I, myself, have not done the whole process exactly but am currently researching it and this is what I have learned thus far. I recall reading before that listening for android.provider.Telephony.WAP_PUSH_RECEIVED will only notify you of an incoming MMS. My understanding is that the intent is more of a receipt and does not actually mean the MMS is in the phone yet, or more precisely, in the database to pull from. That may explain why you are only getting the second to last. If you want to get the MMS when it's finished and ready to be pulled from the database, I think you'll need a ContentObserver to notify you of database updates.
Related
I have already know that how to read the messages from inbox but I want to implement a android app to read only transaction message and display it in a list view with transaction amount ,credit debit etc.For my complete code. current complete code for fetching sms data.how to filter the sms data according to requirement.
public List<SmsInfo> getSmsInfo() {
String[] projection = new String[] { "_id", "address", "person",
"body", "date", "type" };
// #SuppressWarnings("deprecation")
// Cursor cursor = activity.managedQuery(uri, projection, null, null,
// "date desc");
ContentResolver cr = activity.getContentResolver();
Cursor cursor = cr.query(uri, projection, null, null, "date desc");
int nameColumn = cursor.getColumnIndex("person");
int phoneNumberColumn = cursor.getColumnIndex("address");
int smsbodyColumn = cursor.getColumnIndex("body");
int dateColumn = cursor.getColumnIndex("date");
int typeColumn = cursor.getColumnIndex("type");
if (cursor != null) {
int i = 0;
while (cursor.moveToNext() && i++ < 20) {
SmsInfo smsInfo = new SmsInfo();
smsInfo.setName(cursor.getString(nameColumn));
smsInfo.setDate(dateFromLongToString(cursor.getString(dateColumn)));
smsInfo.setPhoneNumber(cursor.getString(phoneNumberColumn));
smsInfo.setSmsbody(cursor.getString(smsbodyColumn));
smsInfo.setType(cursor.getString(typeColumn));
String personName = getPeople2(smsInfo.getPhoneNumber());
smsInfo.setName(null == personName ? smsInfo.getPhoneNumber()
: personName);
infos.add(smsInfo);
}
cursor.close();
}
return infos;
}
Basically transnational messages address contains the some pattern. For eg.
AM-HDFCBK
So seeing that , i have made regular expression to fetch that pattern related messages.
Pattern regEx =
Pattern.compile("[a-zA-Z0-9]{2}-[a-zA-Z0-9]{6}");
protected BroadcastReceiver myReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
final Bundle bundle = intent.getExtras();
try {
if (bundle != null) {
final Object[] pdusObj = (Object[]) bundle.get("pdus");
for (int i = 0; i < pdusObj.length; i++) {
SmsMessage currentMessage;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = bundle.getString("format");
currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i], format);
Log.e("Current Message", format + " : " + currentMessage.getDisplayOriginatingAddress());
} else {
currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
}
Pattern regEx =
Pattern.compile("[a-zA-Z0-9]{2}-[a-zA-Z0-9]{6}");
Matcher m = regEx.matcher(currentMessage.getDisplayOriginatingAddress());
if (m.find()) {
try {
String phoneNumber = m.group(0);
Long date = currentMessage.getTimestampMillis();
String message = currentMessage.getDisplayMessageBody();
Log.e("SmsReceiver Mine", "senderNum: " + phoneNumber + "; message: " + message);
} catch (Exception e) {
e.printStackTrace();
}
} else {
Log.e("Mismatch", "Mismatch value");
}
}
}
} catch (Exception e) {
Log.e("SmsReceiver", "Exception smsReceiver" + e);
}
}
};
So after that you can check that message body contains the word like credited , debited you can access it.
For a quick idea, I would like to suggest you few approaches :
First of all it will be quite challenging to sort any transaction types message from inbox, all you can do is either go through each message and read the body and find out your required messages list but that too wont be feasible.
For instance you have to access address field and do the needful as sms possess all fields such as : address, body, received date and more.
Also as you mentioned you know how to read messages from inbox I am skipping that part. Attaching few links which might help you
ref this library ,one more
use this for reading transaction messages:
private void readMessages(){
final int textViewID = searchView.getContext().getResources().
getIdentifier("android:id/search_src_text", null, null);
final AutoCompleteTextView searchTextView = (AutoCompleteTextView)
searchView.findViewById(textViewID);
try {
Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
mCursorDrawableRes.setAccessible(true);
mCursorDrawableRes.set(searchTextView, 0); //This sets the cursor resource ID to 0 or #null which will make it visible on white background
} catch (Exception e) {}
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
String dateVal = "";
Cursor cursor = this.getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null);
if (cursor.moveToFirst()) { // must check the result to prevent exception
do {
String msgData = cursor.getString(cursor.getColumnIndexOrThrow("body")).toString();
String date = cursor.getString(cursor.getColumnIndexOrThrow("date")).toString();
Long dateV = Long.parseLong(date);
int start = 0;
int end = 0;
String msg = "";
String add = cursor.getString(2);
dateVal = formatter.format(new Date(dateV));
if(!(spam.contains(add) || promo.contains(add))) {
if(msgData.contains("credited")|| msgData.contains("debited") || msgData.contains("withdrawn")) {
messages.add(dateVal + ":" + msgData + "axqw" + add);
contentMessage.add(msgData);
}
}
} while (cursor.moveToNext());
} else {
// empty box, no SMS
}
}
Every transactional message has the following traits:
Sender is always of the format XX-XXXX
The message will have a/c, account no: in it.
There will be keywords like avbl bal, available balance, balance, combined balance, avl bal.
There will be transactional keywords like debited, credited, payment.
If you can conjugate all these conditions then you will find a transactional message.
Here's a lib that might be of some use https://github.com/minimal-scouser/trny
First and foremost, I found this answer particularly helpful. However, it made me wonder how one goes about finding such information.
I can't seem to figure out how to iterate all the messages in my inbox. My current solution uses Uri.parse("content://mms-sms/conversations") in which I give use "_id" and "ct_t". However, it seems I only find the three conversations in my phone despite having 30 msges (20 of them in the save conversation thread and the others divided between two other conversations). Would make sense for such a statement content://mms-sms/conversations. However, the other providers seem to deal only with SMS OR MMS. Isn't there a way to just iterate the entire list of messages in this fashion where I replace "content://mms-sms/conversations" with something else?
public boolean refresh() {
final String[] proj = new String[]{"_id","ct_t"};
cursor = cr.query(Uri.parse("content://mms-sms/conversations"),proj,null,null,null);
if(!(cursor.moveToFirst())) {
empty = true;
cursor.close();
return false;
}
return true;
}
I iterate the messages with a next function
public boolean next() {
if(empty) {
cursor.close();
return false;
}
msgCnt = msgCnt + 1;
Msg msg;
String msgData = cursor.getString(cursor.getColumnIndex("ct_t"));
if("application/cnd.wap.multipart.related".equals(msgData)) {
msg = ParseMMS(cursor.getString(cursor.getColumnIndex("_id")));
} else {
msg = ParseSMS(cursor.getString(cursor.getColumnIndex("_id")));
}
if(!(cursor.moveToNext())) {
empty = true;
cursor.close();
return false;
}
return true;
}
Well, what I am asking doesn't really seem possible.
For those just starting out on such tasks, it's advisable to learn about how content providers work in general. Each Uri value added to the query returns access to specific tables.
Spending some time looking at the different Telephony.Mmssms tables that one can access and it seems, from my testing, that the only table you can access is using "content://mms-sms/conversations as using "content://mms-sms" leads to a null cursor.
Such is life, and it doesn't really make sense to iterate the messages that way since the content and method of extracting the data differ greatly based on whether or not the msg is an SMS or MMS message. It makes sense to iterate and parse SMS and MMS messages separately and store the interesting data into the same class object type for one to manipulate how they would like at a later date.
Useful to such a topic would be the Telephony.Sms documentation. Which is where one can find a descriptions of the column index fields. You can find the same information for Telephony.Mms as well as the sub table Telephony.Mms.Part, with links to each of the base columns to describe the information.
With this being said, here is a solution to the question How can I iterate all the SMS/MMS messages in the phone? and here is the solution that worked for me.
public class Main extends AppCompatActivity {
//Not shown, Overrides, button to call IterateAll();
//implementations to follow
IterateAll();
public void ScanMMS();
public void ScanSMS();
public void ParseMMS(Msg msg);
public Bitmap getMmsImg(String id);
public String getMmsAddr(String id);
}
IterateAll() just calls the two different functions
IterateAll() {
ScanMMS();
ScanSMS();
}
ScanMMS() will iterate through the content://mms table extracting the data from each MMS.
public void ScanMMS() {
System.out.println("==============================ScanMMS()==============================");
//Initialize Box
Uri uri = Uri.parse("content://mms");
String[] proj = {"*"};
ContentResolver cr = getContentResolver();
Cursor c = cr.query(uri, proj, null, null, null);
if(c.moveToFirst()) {
do {
/*String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
System.out.println(str);*/
//System.out.println("--------------------MMS------------------");
Msg msg = new Msg(c.getString(c.getColumnIndex("_id")));
msg.setThread(c.getString(c.getColumnIndex("thread_id")));
msg.setDate(c.getString(c.getColumnIndex("date")));
msg.setAddr(getMmsAddr(msg.getID()));
ParseMMS(msg);
//System.out.println(msg);
} while (c.moveToNext());
}
c.close();
}
}
As one can see, a lot of the important MMS data is in this table, such as the date of the message, the message id and the thread id. You need to use that message ID to pull more information from MMS.
The MMS message is divided into smaller parts of data. Each part contains something different, like an image, or a text portion. You have to iterate each part as I do below.
public void ParseMMS(Msg msg) {
Uri uri = Uri.parse("content://mms/part");
String mmsId = "mid = " + msg.getID();
Cursor c = getContentResolver().query(uri, null, mmsId, null, null);
while(c.moveToNext()) {
/* String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
System.out.println(str);*/
String pid = c.getString(c.getColumnIndex("_id"));
String type = c.getString(c.getColumnIndex("ct"));
if ("text/plain".equals(type)) {
msg.setBody(msg.getBody() + c.getString(c.getColumnIndex("text")));
} else if (type.contains("image")) {
msg.setImg(getMmsImg(pid));
}
}
c.close();
return;
}
Each part as the mid field which corresponds to the id of the message found earlier. We search the MMS part library only for that mms id and then iterate the different parts found. ct or content_type as described in the documentation described what the part is, i.e. text, image, etc. I scan the type to see what to do with that part. If it's plain text, I add that text to the current message body (apparently there can be multiple text parts, but I haven't seen it, but I believe it) and if it's an image, than load the image into a bitmap. I imagine Bitmaps will be easy to send with java to my computer, but who knows, maybe want to just load it as a byte array.
Anyway, here is how one will get the image data from the MMS part.
public Bitmap getMmsImg(String id) {
Uri uri = Uri.parse("content://mms/part/" + id);
InputStream in = null;
Bitmap bitmap = null;
try {
in = getContentResolver().openInputStream(uri);
bitmap = BitmapFactory.decodeStream(in);
if(in != null)
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
You know, I'm not entirely sure how opening an input stream on the content resolver really works and how it is giving me just the image and not like all the other data, no clue, but it seems to work. I stole this one from some different sources while looking for solutions.
The MMS addresses aren't as straight forward to pull as they are for SMS, but here is how you can get them all. The only thing I haven't been able to do is figure out who the sender was. I'd love it if someone knew that.
public String getMmsAddr(String id) {
String sel = new String("msg_id=" + id);
String uriString = MessageFormat.format("content://mms/{0}/addr", id);
Uri uri = Uri.parse(uriString);
Cursor c = getContentResolver().query(uri, null, sel, null, null);
String name = "";
while (c.moveToNext()) {
/* String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
System.out.println(str);*/
String t = c.getString(c.getColumnIndex("address"));
if(!(t.contains("insert")))
name = name + t + " ";
}
c.close();
return name;
}
This was all just for MMS. The good news is that SMS is much simpler.
public void ScanSMS() {
System.out.println("==============================ScanSMS()==============================");
//Initialize Box
Uri uri = Uri.parse("content://sms");
String[] proj = {"*"};
ContentResolver cr = getContentResolver();
Cursor c = cr.query(uri,proj,null,null,null);
if(c.moveToFirst()) {
do {
String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
//System.out.println(str);
System.out.println("--------------------SMS------------------");
Msg msg = new Msg(c.getString(c.getColumnIndex("_id")));
msg.setDate(c.getString(c.getColumnIndex("date")));
msg.setAddr(c.getString(c.getColumnIndex("Address")));
msg.setBody(c.getString(c.getColumnIndex("body")));
msg.setDirection(c.getString(c.getColumnIndex("type")));
msg.setContact(c.getString(c.getColumnIndex("person")));
System.out.println(msg);
} while (c.moveToNext());
}
c.close();
}
Here is my simple message structure so anyone may compile the above code quickly if wanted.
import android.graphics.Bitmap;
/**
* Created by rbenedict on 3/16/2016.
*/
//import java.util.Date;
public class Msg {
private String id;
private String t_id;
private String date;
private String dispDate;
private String addr;
private String contact;
private String direction;
private String body;
private Bitmap img;
private boolean bData;
//Date vdat;
public Msg(String ID) {
id = ID;
body = "";
}
public void setDate(String d) {
date = d;
dispDate = msToDate(date);
}
public void setThread(String d) { t_id = d; }
public void setAddr(String a) {
addr = a;
}
public void setContact(String c) {
if (c==null) {
contact = "Unknown";
} else {
contact = c;
}
}
public void setDirection(String d) {
if ("1".equals(d))
direction = "FROM: ";
else
direction = "TO: ";
}
public void setBody(String b) {
body = b;
}
public void setImg(Bitmap bm) {
img = bm;
if (bm != null)
bData = true;
else
bData = false;
}
public String getDate() {
return date;
}
public String getDispDate() {
return dispDate;
}
public String getThread() { return t_id; }
public String getID() { return id; }
public String getBody() { return body; }
public Bitmap getImg() { return img; }
public boolean hasData() { return bData; }
public String toString() {
String s = id + ". " + dispDate + " - " + direction + " " + contact + " " + addr + ": " + body;
if (bData)
s = s + "\nData: " + img;
return s;
}
public String msToDate(String mss) {
long time = Long.parseLong(mss,10);
long sec = ( time / 1000 ) % 60;
time = time / 60000;
long min = time % 60;
time = time / 60;
long hour = time % 24 - 5;
time = time / 24;
long day = time % 365;
time = time / 365;
long yr = time + 1970;
day = day - ( time / 4 );
long mo = getMonth(day);
day = getDay(day);
mss = String.valueOf(yr) + "/" + String.valueOf(mo) + "/" + String.valueOf(day) + " " + String.valueOf(hour) + ":" + String.valueOf(min) + ":" + String.valueOf(sec);
return mss;
}
public long getMonth(long day) {
long[] calendar = {31,28,31,30,31,30,31,31,30,31,30,31};
for(int i = 0; i < 12; i++) {
if(day < calendar[i]) {
return i + 1;
} else {
day = day - calendar[i];
}
}
return 1;
}
public long getDay(long day) {
long[] calendar = {31,28,31,30,31,30,31,31,30,31,30,31};
for(int i = 0; i < 12; i++) {
if(day < calendar[i]) {
return day;
} else {
day = day - calendar[i];
}
}
return day;
}
}
Some final comments and notes on this solution.
The person field seems to always be NULL and later I plan to implement a contact look up. I also haven't been able to identify who sent the MMS message.
I am not super familiar with java and I am still learning it. I am positive there is a data container (ArrayList) (Vector?) that could hold a user defined object. And if sortable by a specific field in the object (date), one could iterate that list and have a chronological order of all the message: both MMS/SMS and both sent/received.
Isn't there a way to just iterate the entire list of messages in this fashion where I replace "content://mms-sms/conversations" with something else?
It is possible to get all MMS and SMS messages in a single query using the content://mms-sms/complete-conversations URL. For some odd reason, there is no Uri field for this in the Telephony.MmsSms class, but it's been available since at least Froyo.
Using this single query will certainly be more efficient than querying the tables separately, and any sorting, grouping, or filtering that needs to be done will definitely be faster performed by the SQLite engine than by manipulating Java collections.
Please note that you must use a specific projection for this query. You cannot pass null or the * wildcard. Furthermore, it would be advisable to include MmsSms.TYPE_DISCRIMINATOR_COLUMN ("transport_type") in your projection - which will have a value of either "mms" or "sms" - to easily distinguish the message type.
The selection, selectionArgs, and orderBy arguments work as usual, and null can be passed for any or all of them.
Background
I've found that there is a way to open a specific contact conversation screen on WhatsApp, here .
Not only that, but I've found that an app called "Drupe" does the same, and maybe even more :
https://lh3.googleusercontent.com/EQrs1jplMlP8SkOTdpqT4NzmgzGa5Wz2qageG1Pkjc6rKg0HBb-rwlOVW07_G7bAWgo=h900
The problem
I can't find any official API of opening it this way, so I'm not sure how safe it is.
I've found SDKs, but not intents instructions.
The questions
I'd like to know more about what's available for various social-networks and chatting apps :
WhatsApp
Facebook Messenger
Viber
Line
Telegram
Hangouts
Possible features may be:
open the conversation of a contact, when input is his phone number
have a new text that will be ready to be sent in the new screen
for Facebook, maybe also be able to open using the Facebook-ID of the person (meaning this is the input), instead of a phone number.
Are such features available for each of those social networks and chatting apps?
For Facebook-messenger, I've found this (from https://developers.facebook.com/docs/messenger-platform/discovery/m-me-links#format):
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://m.me/" + facebookId));
It works, but I wonder if there is another way to access it (using phone number, for example).
For WhatsApp, I've found this (from here) :
final String formattedPhoneNumber = getFormattedPhoneNumber(this, phone);
final String contactId = getContactIdFromPhoneNumber(phone);
final String contactMimeTypeDataId = getContactMimeTypeDataId(contactId, "vnd.android.cursor.item/vnd.com.whatsapp.profile");
if (contactMimeTypeDataId != null) {
intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + formattedPhoneNumber));
intent.setPackage("com.whatsapp");
} else
Toast.makeText(this, "cannot find this contact on whatsapp", Toast.LENGTH_SHORT).show();
public static String getFormattedPhoneNumber(Context context, String input) {
final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
String normalizedPhone = input.replaceAll("[^0-9+]", "");
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String countryCode = tm.getSimCountryIso();
final PhoneNumber phoneNumber = phoneNumberUtil.parse(normalizedPhone, countryCode.toUpperCase());
final String formattedPhoneNumber = phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164).replaceAll("[^0-9]", "");
return formattedPhoneNumber;
} catch (NumberParseException e) {
e.printStackTrace();
}
return null;
}
private String getContactIdFromPhoneNumber(String phone) {
if (TextUtils.isEmpty(phone))
return null;
final Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone));
final ContentResolver contentResolver = getContentResolver();
final Cursor phoneQueryCursor = contentResolver.query(uri, new String[]{PhoneLookup._ID}, null, null, null);
if (phoneQueryCursor != null) {
if (phoneQueryCursor.moveToFirst()) {
String result = phoneQueryCursor.getString(phoneQueryCursor.getColumnIndex(PhoneLookup._ID));
phoneQueryCursor.close();
return result;
}
phoneQueryCursor.close();
}
return null;
}
public String getContactMimeTypeDataId(#NonNull Context context, String contactId, #NonNull String mimeType) {
if (TextUtils.isEmpty(mimeType))
return null;
ContentResolver cr = context.getContentResolver();
Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, new String[]{Data._ID}, Data.MIMETYPE + "= ? AND "
+ ContactsContract.Data.CONTACT_ID + "= ?", new String[]{mimeType, contactId}, null);
if (cursor == null)
return null;
if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
String result = cursor.getString(cursor.getColumnIndex(Data._ID));
cursor.close();
return result;
}
It works, but it doesn't add the message. It also might say the contact doesn't have WhatsApp.
It's also possible to just use the phone number, as I wrote here.
For Viber, I've found this (from here) :
final String contactId = getContactIdFromPhoneNumber(phone);
final String contactMimeTypeDataId = getContactMimeTypeDataId(contactId, "vnd.android.cursor.item/vnd.com.viber.voip.viber_number_message");
if (contactMimeTypeDataId != null) {
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("content://com.android.contacts/data/" + contactMimeTypeDataId));
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
intent.setPackage("com.viber.voip");
} else {
intent = new Intent("android.intent.action.VIEW", Uri.parse("tel:" + Uri.encode(formattedPhoneNumber)));
intent.setClassName("com.viber.voip", "com.viber.voip.WelcomeActivity");
}
private String getContactIdFromPhoneNumber(String phone) {
final Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone));
final ContentResolver contentResolver = getContentResolver();
final Cursor phoneQueryCursor = contentResolver.query(uri, new String[]{PhoneLookup._ID}, null, null, null);
if (phoneQueryCursor != null) {
if (phoneQueryCursor.moveToFirst()) {
String result = phoneQueryCursor.getString(phoneQueryCursor.getColumnIndex(PhoneLookup._ID));
phoneQueryCursor.close();
return result;
}
phoneQueryCursor.close();
}
return null;
}
For Hangouts, it seems it's similar to Viber, but with this mimetype: "vnd.android.cursor.item/vnd.googleplus.profile.comm". Yet, it doesn't work as it probably needs additional steps (setting G+ to keep contacts updated and have the contacts in the G+ circles). However, I've somehow succeeded to open the video chat of a person:
intent =new Intent(Intent.ACTION_VIEW,Uri.parse("content://com.android.contacts/data/"+contactMimeTypeDataId));
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT |Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
For Telegram, someone (here) suggested using the next code, but it doesn't work:
intent = new Intent(android.content.Intent.ACTION_SENDUri.parse("http://telegram.me/"+profile)));
intent.setPackage("org.telegram.messenger");
It's also possible to just use the phone number, as I wrote here.
For Line, I've found these (based on here and here), but none work:
Intent intent = new Intent("jp.naver.line.android.intent.action.LINESHORTCUT");
intent.putExtra("shortcutType", "chatmid");
intent.putExtra("shortcutTargetId", target);
intent.putExtra("shortcutTargetName", "");
intent.putExtra("shortcutFromOS", false);
startActivity(intent);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("line://msg/text/" + getMongon()));
skype: this one works (found from various links, here, for example):
final String skypeUserName = getSkypeUserName(phone);
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("skype:" + skypeUserName + "?chat"));
public String getSkypeUserName(String phoneNumber) {
if (TextUtils.isEmpty(phoneNumber))
return null;
ContentResolver cr = getContentResolver();
final Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
Cursor cursor = cr.query(uri, new String[]{PhoneLookup.LOOKUP_KEY}, null, null, null);
if (cursor == null)
return null;
final Set<String> contactKeys = new HashSet<>();
// get contact keys
{
final int contactKeyIdx = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
String contactKey = cursor.getString(contactKeyIdx);
contactKeys.add(contactKey);
}
cursor.close();
}
if (contactKeys.isEmpty())
return null;
//get raw ids
final Set<String> contactRawIdsSet = new HashSet<>();
{
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < contactKeys.size(); ++i)
sb.append(sb.length() == 0 ? "?" : ",?");
String inParameters = sb.toString();
final String[] selectionArgs = contactKeys.toArray(new String[contactKeys.size()]);
cursor = cr.query(ContactsContract.Data.CONTENT_URI, new String[]{ContactsContract.Data.RAW_CONTACT_ID}, ContactsContract.Data.LOOKUP_KEY + " IN (" + inParameters + ")", selectionArgs, null);
if (cursor == null)
return null;
final int rawContactColIdx = cursor.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID);
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
String rawContactId = cursor.getString(rawContactColIdx);
contactRawIdsSet.add(rawContactId);
}
cursor.close();
}
if (contactRawIdsSet.isEmpty())
return null;
//find the skype name
//TODO think of a better way to query, as it looks weird to search within a set of ids...
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < contactRawIdsSet.size(); ++i)
sb.append(sb.length() == 0 ? "?" : ",?");
String inParameters = sb.toString();
final String[] selectionArgs = new String[2 + contactRawIdsSet.size()];
selectionArgs[0] = "com.skype.contacts.sync";
selectionArgs[1] = "vnd.android.cursor.item/name";
int i = 2;
for (String rawId : contactRawIdsSet)
selectionArgs[i++] = rawId;
cursor = cr.query(ContactsContract.Data.CONTENT_URI, new String[]{RawContacts.SOURCE_ID}, ContactsContract.RawContacts.ACCOUNT_TYPE + " = ? AND " + Data.MIMETYPE + " = ? AND " +
ContactsContract.Data.CONTACT_ID + " IN (" + inParameters + ")", selectionArgs, null);
if (cursor == null)
return null;
if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
String result = cursor.getString(cursor.getColumnIndex(RawContacts.SOURCE_ID));
cursor.close();
return result;
}
it works for me
try {
String toNumber = "+91 8*******36"; // contains spaces.
toNumber = toNumber.replace("+", "").replace(" ", "");
Intent sendIntent = new Intent(Intent.ACTION_SENDTO,Uri.parse("smsto:" + "" + toNumber + "?body=" + ""));
sendIntent.putExtra(Intent.EXTRA_TEXT, "hello");
sendIntent.setPackage("com.whatsapp");
startActivity(sendIntent);
}
catch (Exception e){
Toast.makeText(getActivity(),"it may be you dont have whats app",Toast.LENGTH_LONG).show();
}
Other posts here have good information. I wanted to add for LINE because the information is lacking in many places.
String userId = findUserId();
String sendText = "line://ti/p/~" + userId;
Intent intent = null;
try {
intent = Intent.parseUri(sendText, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
e.printStackTrace();
}
startActivity(intent);
I am fed up of this issue, don't know actually where is problem, Below is my code :
private void readSMS() throws IOException {
// TODO Auto-generated method stub
Log.d("Read SMS","Called");
ContentResolver cr = context.getContentResolver();
Uri uri = Uri.parse("content://sms/inbox");
StringBuilder smsBackup = new StringBuilder();
Cursor messagesCursor = cr.query(uri, new String[] { "_id","address","body","person"}, null,null, null);
smsBackup.append("SMS Back UP (Total Message(s)::"+messagesCursor.getCount()+") \n\n");
String name = null;
if(messagesCursor.getCount() > 0){
while(messagesCursor.moveToNext()){
name = null;
name = getName(messagesCursor.getString(messagesCursor.getColumnIndex("address")));
if(name==null)
name = "Sender : " + messagesCursor.getString(messagesCursor.getColumnIndex("address"));
smsBackup.append("Sender : "+name +"\n"+ "Message : "+messagesCursor.getString(messagesCursor.getColumnIndex("body")) + "\n\n");
}
}
Log.d("InSMS Lenght","::"+smsBackup.toString().length());
}
here is log cat message : W/CursorWrapperInner(8375): Cursor finalized without prior close()
You should close the Cursor object once you're done working with it.
This may be old but here is the reason why.
Don't throw the exception here:
readSMS() throws IOException
instead wrap in a a try catch block like so:
try{
ContentResolver cr = context.getContentResolver();
Uri uri = Uri.parse("content://sms/inbox");
StringBuilder smsBackup = new StringBuilder();
Cursor messagesCursor = cr.query(uri, new String[] { "_id","address","body","person"}, null,null, null);
smsBackup.append("SMS Back UP (Total Message(s)::"+messagesCursor.getCount()+") \n\n");
String name = null;
if(messagesCursor.getCount() > 0){
while(messagesCursor.moveToNext()){
name = null;
name = getName(messagesCursor.getString(messagesCursor.getColumnIndex("address")));
if(name==null)
name = "Sender : " + messagesCursor.getString(messagesCursor.getColumnIndex("address"));
smsBackup.append("Sender : "+name +"\n"+ "Message : "+messagesCursor.getString(messagesCursor.getColumnIndex("body")) + "\n\n");
}
}
messagesCursor.close();
}catch(IOException e){
//handle here, if not log it
}finally{
//can also close here if you want, need to wrap in another try block and check for null
}
Seems like the problem is the ioexception is being caught before the close() could be called. The method is then thrown at that point and is never called. I could be wrong, I just took a quick glance at it. I hope this helps.
I want to access the sms logs also . but I don't find any way , I already accessed the call logs using CallLog.Calls ,it's unable to list sms logs .
below code is listing only call logs , but not all logs even sms logs are not listed (sms logs means logs created because received and sent sms) . please help me to find the way to retrieve the sms logs . please ans if possible sir .
Cursor cursor = managedQuery(CallLog.Calls.CONTENT_URI, null, null,
null, Calls.DATE + " DESC");
// cursor is inited
cursor.moveToFirst();
String name;
String number;
int type;
do {
name = "";
number = "";
type = -1;
try {
number = cursor.getString(cursor
.getColumnIndex(CallLog.Calls.NUMBER));
type = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
try {
name = cursor.getString(cursor
.getColumnIndex(CallLog.Calls.CACHED_NAME));
} catch (Exception e) {
} finally {
if (name == null || name.equals("")) {
name = "UNKNOWN";
}
}
Log.e("My App", name + " : "+number + " : "+type);
} catch (Exception e) {
Log.e("My App", "Error in creation");
}
} while (cursor.moveToNext());
this is just suggestion. you get better answer rather then this...
see if you want to get sms log then use below code.. and you already get call log.. so if you want to mix call and sms log in one list then you must have to do that by date..
in call log you get date & time
in sms also you get date & time
sms log code...
put this in method and use your own way..
CharSequence contentTitle = getString(R.string.app_name);
final ProgressDialog progDailog = ProgressDialog.show(
All_logs_tab.this, contentTitle, "Please wait...", true);
final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
/* finish sms load */
}
};
new Thread() {
public void run() {
try {
Uri myMessage = Uri.parse("content://sms/");
ContentResolver cr = con.getContentResolver();
Cursor c = cr.query(myMessage, new String[] { "_id",
"address", "date", "body", "read" }, null,
null, null);
startManagingCursor(c);
getSmsLogs(c, con);
} catch (Exception e) {
}
handler.sendEmptyMessage(0);
progDailog.dismiss();
}
}.start();
.................................................
i added all sms details in array list
ArrayList<String> sms_id = new ArrayList<String>();
ArrayList<String> sms_num = new ArrayList<String>();
ArrayList<String> sms_Name = new ArrayList<String>();
ArrayList<String> sms_dt = new ArrayList<String>();
ArrayList<String> sms_body = new ArrayList<String>();
........................................................
public void getSmsLogs(Cursor c, Context con) {
if (sms_num.size() > 0) {
sms_id.clear();
sms_num.clear();
sms_Name.clear();
sms_body.clear();
sms_dt.clear();
}
try {
if (c.moveToFirst()) {
do {
if (c.getString(c.getColumnIndexOrThrow("address")) == null) {
c.moveToNext();
continue;
}
String _id = c.getString(c.getColumnIndexOrThrow("_id"))
.toString();
String Number = c.getString(
c.getColumnIndexOrThrow("address")).toString();
String dat = c.getString(c.getColumnIndexOrThrow("date"))
.toString();
String as = (String) get_dt(dat, "dd/MM/yyyy, hh.mma");
String Body = c.getString(c.getColumnIndexOrThrow("body"))
.toString();
String name = getContactDisplayNameByNumber("" + Number,
con);
if (name.length() <= 0 || name.length() == 1) {
name = "no name";
}
sms_id.add(_id);
sms_num.add(Number);
sms_Name.add("" + name);
sms_body.add(Body);
sms_dt.add(as);
} while (c.moveToNext());
}
c.close();
} catch (Exception e) {
e.printStackTrace();
}
}
You need the URI to the SMS messages's table this will retrieve you the data. It can be found in the base source code, however; it is not recommended by Google to do this.
use this link Android call logs to retrieve call logs. for sms do the same by using SMS_URI="content://sms" , but it is not recommended as it is not public URi.