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.
Related
I am building an app , there is a requirement of notifying app when a new contact added or existing contact gets edited or removed. Although a lot of questions are already available those have answers as well. But my question is little bit different. I am using approach of Content Observer as mentioned below in the code
ContactChangeObserver contactChangeObserver = new ContactChangeObserver(this, new Handler());
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactChangeObserver);
and in ContactChangeObserver class I am overriding onChange() method as it was already recommended in many of the post. Code is mentioned below
public class ContactChangeObserver extends ContentObserver {
private Context mContext;
public ContactChangeObserver(Context context, Handler handler) {
super(handler);
mContext = context;
}
#Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
List<ContactsModel> listOfDBContact = new ChildTrackingDB().getAllContacts(ChildTrackingDB.getInstance(mContext));
List<ContactsModel> listOfCellPhoneContact = ContactUtility.readContactDirectoryOfPhone(mContext);
if (listOfCellPhoneContact.size() == listOfDBContact.size()) {
//this is edit case
} else if (listOfCellPhoneContact.size() > listOfDBContact.size()) {
//this is add case
}else {
//this is remove case
}
}
}
I am getting call back of onChange() as expected(in all cases of adding, removing and editing case). As mentioned in above example, I am overriding onChange() that has URI param. And when I get call back i also receive uri as well. my question is that can that uri be useful to only get that contact which got changed or added? The uri I am getting is
content://com.android.contacts
In my example code, if contact is edited and the device has let's suppose more than thousands contacts then it is a very time consuming to iterate over each contact.Or is there any better approach available for the problem.
I've faced a similar kind of problem. First of all the Uri which you are getting in onChange() method is vague. From my previous encounter I can tell, You won't be able to detect which contact got inserted, updated or deleted. So yes it becomes very time-consuming to detect which contact to get and perform Crud.
To answer your second question, I would suggest you to use Set instead of List. Here is a sample class which you might helpful. It's a linear operation and i've tested it with 2-3k+ data. And it performs well.
public class AddressBookObserver extends ContentObserver {
private static final String TAG = "AddressBookObserver";
private static final String FLAG_INSERT = "INSERT";
private static final String FLAG_DELETE = "DELETE";
private long lastTimeOfCall = 0L;
private long lastTimeOfUpdate = 0L;
private long threshold_time = 5000;
private WeakReference<Context> mContextWeakReference;
public AddressBookObserver(Handler handler, Context context) {
super(handler);
this.mContextWeakReference = new WeakReference<Context>(context);
}
#Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
}
#Override
public void onChange(boolean selfChange, Uri uri) {
Log.d(TAG, "onChange() Address Book Changed!");
lastTimeOfCall = System.currentTimeMillis();
Set<String> phoneBookSet = new HashSet<>();
Set<String> providerSet = new HashSet<>();
if (checkContactPermission() && (lastTimeOfCall - lastTimeOfUpdate > threshold_time) && General.getIsContactListImported(mContextWeakReference.get())) {
lastTimeOfUpdate = System.currentTimeMillis();
phoneBookSet.addAll(getOnlyPhoneNumbers());
providerSet.addAll(getProviderNumbers());
int bookCount = phoneBookSet.size();
int providerCount = providerSet.size();
Log.e(TAG, "onChange: bookCount: " + bookCount + " providerCount: " + providerCount);
if (bookCount > providerCount) {
Log.i(TAG, "onChange() Insert!");
phoneBookSet.removeAll(providerSet);
String val = phoneBookSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.w(TAG, "value to insert: " + val);
//DO Insert Operations
} else if (bookCount < providerCount) {
Log.i(TAG, "onChange() DELETE!");
providerSet.removeAll(phoneBookSet);
String val = providerSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.w(TAG, "value to delete: " + val);
//Do Delete Operations
} else {
Log.i(TAG, "onChange() UPDATE!");
Set<String> tempPhoneBookSet = new HashSet<>();
tempPhoneBookSet.addAll(phoneBookSet);
phoneBookSet.removeAll(providerSet);
String newData = phoneBookSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.e(TAG, "newData: " + newData);
providerSet.removeAll(tempPhoneBookSet);
String deleteData = providerSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.e(TAG, "deleteData: " + deleteData);
if (!newData.equals(deleteData)) {
//DO Update Operations
} else {
Log.i(TAG, "onChange() Nothing to update!");
}
}
} else if (!checkContactPermission()) {
Log.e(TAG, "onChange() Contact Permission not granted!");
} else {
Log.e(TAG, "onChange() Time threshold not reached Or Contacts not imported yet!");
}
}
private boolean checkContactPermission() {
return ContextCompat.checkSelfPermission(mContextWeakReference.get(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
}
/**
* Get only phone numbers from device's addressBook
*
* #return - Set of distinct phone numbers
*/
private Set<String> getOnlyPhoneNumbers() {
Log.d(TAG, "getOnlyPhoneNumbers()");
Cursor phones = mContextWeakReference.get().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " ASC");
Set<String> uniquePhoneContacts = new HashSet<>();
while (phones.moveToNext()) {
String name = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)).replaceAll("\\D", "");
String id = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));
String photoUri = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
if (uniquePhoneContacts.add(id + "," + name + "," + photoUri + "," + phoneNumber)) {
Log.w(TAG, "Book#" + id + "," + name + "," + photoUri + "," + phoneNumber);
}
}
phones.close();
return uniquePhoneContacts;
}
/**
* Get all the Contacts from provider which are not deletable
*
* #return - All distinct phone numbers from app's provider
*/
private Set<String> getProviderNumbers() {
Log.d(TAG, "getProviderNumbers()");
Cursor phones = mContextWeakReference.get().getContentResolver().query(MyContactProvider.CONTENT_URI, null, PhoneContactController.COL_IS_DELETABLE + "=?",
new String[]{"0"}, MyContactProvider.COL_CONTACT_ID + " ASC");
Set<String> uniquePhoneContacts = new HashSet<>();
while (phones.moveToNext()) {
String id = phones.getString(phones.getColumnIndex(MyContactProvider.COL_CONTACT_ID));
String name = phones.getString(phones.getColumnIndex(MyContactProvider.COL_NAME));
String phoneNumber = phones.getString(phones.getColumnIndex(MyContactProvider.COL_CONTACT_NO));
String photoUri = phones.getString(phones.getColumnIndex(MyContactProvider.COL_PHOTO_URI));
if (uniquePhoneContacts.add(id + "," + name + "," + photoUri + "," + phoneNumber)) {
Log.w(TAG, "QueryProvider#" + id + "," + name + "," + photoUri + "," + phoneNumber);
}
}
phones.close();
return uniquePhoneContacts;
}
}
I hope this helps.
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
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.
I'm writing an application, which have to help me get all information about browser history, so I wrote a simple code:
public class WebHistory {
private Context context;
private Cursor cr;
public StringBuilder sb;
public WebHistory(Context c){
this.context = c;
}
public void takeHistory(){
cr = context.getContentResolver().query(Browser.BOOKMARKS_URI,Browser.HISTORY_PROJECTION, null, null, null);
cr.moveToFirst();
String title = "";
String date = "";
String visits = "";
String url = "";
String info = "";
if(cr.moveToFirst() && cr.getCount() > 0){
while(cr.isAfterLast() == false){
title = cr.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
date = cr.getString(Browser.HISTORY_PROJECTION_DATE_INDEX);
url = cr.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
visits = cr.getString(Browser.HISTORY_PROJECTION_VISITS_INDEX);
info = title + " date: " + date + " url: " + url + " visits" + visits + "\n";
Toast.makeText(context, info, Toast.LENGTH_LONG).show();
cr.moveToNext();
}
}
}
}
Method takeHistory() helps me to take some data about browser history, but I need more functionality, like:
- HISTORY_PROJECTION_DATE_INDEX gives my only one date, and I need all dates (and also hours) when the user visited this page
- Browser.HISTORY_PROJECTION_VISITS_INDEX returns all visits which I made, but I want to divide this amount into gruops of visits which took place at the specified timestamp
Can anybody suggest how can I cull this information or recommend a tutorial, in which I can find necessary information? Thank you in advance for your advice.
You will need to start content observor and record all the changes that occur. I have done similar code. Start a content observor and in the onChange(); function, read the history that has changed since last time you read it(you can use shared preferences for that). And you need to do this all in a service
public void onChange(boolean selfChange) {
super.onChange(selfChange);
/**
* Get SharedPreferneces of the user
*/
SharedPreferences pref= myContext.getSharedPreferences("com.tpf.sbrowser",
Context.MODE_PRIVATE);
long wherelong = pref.getLong("Date", 0);
DatabaseManager db=new DatabaseManager(myContext,1);
String[] proj = new String[] { Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.URL, BookmarkColumns.DATE,};
String sel = Browser.BookmarkColumns.BOOKMARK + " = 0";
Cursor mCur = myContext.getContentResolver().query(
Browser.BOOKMARKS_URI, proj, sel, null, null);
Log.d("onChange", "cursorCount"+mCur.getCount());
mCur.moveToFirst();
String title = "";
String url = "";
long lastVisitedDate=0;
//You will need to create a database manager to manage your database and use its helper functions
DbMessage msg = new DbMessage(lastVisitedDate,url, title);
/**
* Start reading the user history and dump into database
*/
if(mCur.moveToFirst() && mCur.getCount() > 0) {
while (mCur.isAfterLast() == false) {
title =mCur.getString(0);
url = mCur.getString(1);
lastVisitedDate =mCur.getLong(2);
if ((lastVisitedDate>wherelong) && (!title.equals(url))) {
msg.set(lastVisitedDate, url, title);
db.InsertWithoutEnd(msg);
pref.edit().putBoolean("BrowserHistoryRead", true).commit();
pref.edit().putLong("Date", lastVisitedDate).commit();
myContext.updateTime(wherelong,lastVisitedDate);
wherelong=lastVisitedDate;
}
mCur.moveToNext();
}
}
mCur.close();
}
}
/**
* (non-Javadoc)
* #see android.app.Service#onDestroy()
*/
#Override
public void onDestroy() {
super.onDestroy();
getApplication().getContentResolver().unregisterContentObserver(
observer);
Toast.makeText(this, "Service destroyed ...", Toast.LENGTH_LONG).show();
state = 0;
}
}
I'm new in android and Java world and I want to edit cursor data result before displaying it. To do so I wrote the following code
private static final String[] INTERNAL_COLUMNS_1 = new String[] {
MediaStore.Audio.Media._ID,MediaStore.Audio.Media.TITLE};
private static final String[] INTERNAL_COLUMNS_2 = new String[] {
MediaStore.Audio.Media._ID, "fixString(" + MediaStore.Audio.Media.TITLE + ")"};
private Cursor getInternals() {
return query(
MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS_1,
constructBooleanTrueWhereClause(mFilterColumns),
null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
}
private Cursor mCursor = new SortCursor(getInternals());
public String fixString(String oldstr)
{
String str = new String(oldstr);
str = str.replace("_", " ");
if (Character.isLowerCase(str.charAt(0)))
{
str = str.substring(0,1).toUpperCase() + str.substring(1,str.length());
}
for(int i=1;i<str.length();i++)
{
if ((Character.isUpperCase(str.charAt(i)))
&& (Character.isLetter(str.charAt(i)))
&& (Character.isLowerCase(str.charAt(i - 1)))
&& (Character.isLetter(str.charAt(i - 1))))
{
str = str.substring(0,i) + " " + str.substring(i,str.length());
}
}
return str;
}
public static void main(){
for (int i = 0; i < mCursor.getCount(); i++)
{
mCursor.moveToPosition(i);
String string = mCursor.getString(1);
Log.d("OUT"," string = "+ string);
}
Using INTERNAL_COLUMNS_1, the log shows
TomSkinner
Jack_Smith
foxRayan
I want to have as the cursor result :
Tom Skinner
Jack Smith
Fox Rayan
So I used INTERNAL_COLUMNS_2, but I have exception
android.database.sqlite.SQLiteException: no such function: fixString
Does any one know how to do so, or any other idea ?
You need to either change the data before it gets into the database, or change it before you display it. What you are trying to do is create a function and use it in a SQLite query, which will not work. Removing the fixString parts from the SQL query and then using it on the string from the cursor is one way it will work.
for (int i = 0; i < mCursor.getCount(); i++)
{
mCursor.moveToPosition(i);
String string = mCursor.getString(1);
Log.d("OUT"," string = "+ fixString(string));
}