Bounty Award - The bounty will be awarded to an answer that gets from a populated Telephony.Sms.Inbox.PERSON value, to the associated Contact using only ContractsContact tables.
I'm reading SMS messages in the standard way in my application:
final String[] projection = {Telephony.Sms.Inbox.BODY,
Telephony.Sms.Inbox.ADDRESS,
Telephony.Sms.Inbox.READ,
Telephony.Sms.Inbox.DATE,
Telephony.Sms.Inbox.PERSON};
final Cursor cursor = ctx.getContentResolver().query(Telephony.Sms.Inbox.CONTENT_URI,
projection, null, null, Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);
When populated, the id returned from the index Telephony.Sms.Inbox.PERSON relates to the id of the deprecated Contacts.People._ID and can be used to query further contact information in the following way:
final String[] projection = {Contacts.People.DISPLAY_NAME};
final String[] selectionArgs = {contactId};
final Cursor cursor = ctx.getContentResolver().query(Contacts.People.CONTENT_URI,
projection, Contacts.People._ID + " = ?", selectionArgs, null);
Why would the relatively new Telephony API use deprecated tables, instead of ContactsContract?
Telephony.Sms.Inbox.PERSON documentation states:
Type: INTEGER (reference to item in content://contacts/people)
I've tried unsuccessfully (but not unsurprisingly?) to find a mapping to the id in any of the ContactsContract id fields, so I'm left having to use deprecated APIs in order to resolve the queries I need to perform quickly.
Such queries include searching for messages by a particular contact, for which I only have the name. The contact could have multiple numbers, which may not be in the correct format to potentially match Telephony.Sms.Inbox.ADDRESS entries.....
The workaround of using Telephony.Sms.Inbox.ADDRESS and ContactsContract.PhoneLookup is not the end of the world when going from the number to the contact, but I still feel I must be missing something here?
Here is the process I'm using to get the messages for 'Joe Bloggs'.
1) Query the ContactsContract table to confirm a contact by the name of Joe Bloggs exists on the device - or get a close match if the contact is actually listed as 'Joe Blogs'.
2) Using the confirmed name, I query the deprecated Contact.People table to get all associated ids for the contact in the following way:
final String selection = Contacts.People.DISPLAY_NAME + " LIKE ?";
final String[] projection = {Contacts.People.DISPLAY_NAME,
Contacts.People._ID};
final String[] selectionArgs = {contactName};
final Cursor cursor = ctx.getContentResolver().query(Contacts.People.CONTENT_URI,
projection, selection, selectionArgs, null);
3) Using the list of deprecated contact ids, I query the message table as so:
final String[] referredArgs = new String[contactIdArray.size()];
for (int i = 0; i < contactIdArray.size(); i++) {
referredArgs[i] = contactIdArray.get(i);
}
final String referredSelection = Telephony.Sms.Inbox.PERSON + " IN "
+ "(" + TextUtils.join(",", referredArgs) + ")";
final String[] projection = {Telephony.Sms.Inbox.BODY,
Telephony.Sms.Inbox.ADDRESS,
Telephony.Sms.Inbox.READ,
Telephony.Sms.Inbox.DATE,
Telephony.Sms.Inbox.PERSON};
final Cursor cursor = ctx.getContentResolver().query(Telephony.Sms.Inbox.CONTENT_URI,
projection, referredSelection, null, Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);
I'm hoping someone will tell me I'm going round the houses here and there is a more obvious solution using current APIs. I don't consider iterating the entire message table using ContactsContract.PhoneLookup an optimised solution.
Thanks in advance.
I wouldn't use the Telephony.Sms.Inbox.PERSON field, and definitely wouldn't query the deprecated People apis if I were you.
The People apis had been deprecated for so long you can't count on all devices our there to properly support it anymore.
First thing you need to understand is that there isn't a one-to-one link between sms and contacts.
An SMS can come from a non-contact phone number, a single contact, multiple contacts, a mixture of contacts and non-contacts, alpha-numeric ids, and even other, more rare options.
Next thing, you should read carefully the stock code and how it handles a properly called "Recipient ID" that you can get from the SMS collection, there's a collection called canonical-addresses (or canonical-address) that serves as a mapping between a phone number (or a comma-separated list of phones) and a recipient id.
The code does a single query on launch to cache the entire table in memory, and then uses it to map between phones and recipient-ids.
Here's the mapping class
Why would the relatively new Telephony API use deprecated tables, instead of ContactsContract?
What you are referring to is not new. In Telephony.java, you see it relies on the existing content://sms provider:
public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
/**
* The {#code content://} style URL for this table.
*/
public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");
It was already there in Donut (and probably before, but I didn't check).
What's new in Kitkat is the ability to change the SMS app.
It's been five years and it's still relevant. You still need to do endlessly phoneLookup and hang up callbacks on contact tables if all you need to do is synchronize text messages.
I do not understand your concern properly but I am working on similar project, here is the basic code, and basic, important columns for fetching and display a message:
ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"*"};
Cursor SMSL = contentResolver.query(Telephony.Sms.CONTENT_URI, projection, null, null, "date ASC");
int msgscount = SMSL.getCount();
if (msgscount>0) {
msgs = new String[SMSL.getCount()][msgs_column_count];
int i = 0;
while (SMSL.moveToNext()) {
progress.setProgress(i);
msgs[i][0] = SMSL.getString(SMSL.getColumnIndex("address"));
msgs[i][1] = SMSL.getString(SMSL.getColumnIndex("date_sent"));
msgs[i][2] = SMSL.getString(SMSL.getColumnIndex("date"));
msgs[i][3] = SMSL.getString(SMSL.getColumnIndex("type"));
msgs[i][4] = SMSL.getString(SMSL.getColumnIndex("body"));
msgs[i][5] = SMSL.getString(SMSL.getColumnIndex("read"));
if (SMSL.getString(SMSL.getColumnIndex("service_center")) != null){
msgs[i][6] = SMSL.getString(SMSL.getColumnIndex("service_center"));
}else{
msgs[i][6] = "";
}
i++;
}
SMSL.close();
}else{
msgs = new String[0][0];
Toast.makeText(getApplicationContext(),"No messages found!",Toast.LENGTH_LONG).show();
}
If you want any help with this or fetching messages, let me know.
I'm getting following exception reported by few users of my application.However I'm not getting the same when trying on my local device.
Would need advice as to how to approach towards solving the problem.
java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
at android.database.CursorWindow.nativeGetLong(Native Method)
at android.database.CursorWindow.getLong(CursorWindow.java:507)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:75)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:220)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:237)
Following are the details for the context:
The exception is coming on the table storing the images as blob. The table definition is
photo_id String,
blob_image Blob
blob_date String
The code to get the blob data is as following
byte[] photo= null;
Cursor cursor = null;
String criteria = " photo_id = ?";
String[] arguments = {photoid};
try {
cursor = database.query("MY_GALLERY", null,criteria, arguments, null, null, null);
if(null !=cursor){
int rowCount = cursor.getCount();
if(rowCount>0){
cursor.moveToFirst();
int colIndex = cursor.getColumnIndex("blob_image");
if (colIndex != -1) {
photo = cursor.getBlob(colIndex));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
As I'm trying to fetch blob value only if the row count is greater than 0 after checking cursor.getCount() I find it difficult
why cusrsor initialization is being raised as issue.
I also tried to check the size of image being saved. They are less than 400kb in size.
This error is also reported from users using Nexus 7 with Android version 4.4.4 and Samsung tablet SM-T116NY with android version 4.4.
The application is in use for over two years and I'm getting this issue for the first time .
I've checked similar and related issues already and most of the suggestions mentioned [with respect appropriate data base initialisation,
handling of cursor index, opening/closing of cursor, row size limit less than 2mb are taken care of]
One of the reason to store this in DB is to have an alternate storage where user do not delete stuff by mistake.
In application the image from DB is used only if the image is not found in file system.
Any recommendation or suggestion?
thanks
Pradeep
in my application I am displaying a list of items queried from the database with the use of Loader Manager and Content Provider. The results are attached to a SimpleCursorAdapter and then displayed on the List View. I would like to be able to load, let's say up to 20 items at first and when scrolling to the end of that list, load up additional 20 items. Up to now, I used similar approach as here:
How to update listview whose data was queried from database through SimpleCursorAdapter?
i.e. limiting query results to 20 and increasing the number appropriately in the OnScrollListener and restarting the loader with new instructions.
The problem is with this statement:
incomeAdapter.swapCursor(data);
It completely resets adapter's data, therefore only the second batch of items are displayed on the list. Is there any way to simply load additional items on top of existing ones? Thanks
EDIT:
Merging cursors like this:
if (incomeAdapter.getCursor() != null){
Log.v("Income Activity", "current adapter");
Cursor oldData = incomeAdapter.getCursor();
MergeCursor merge = new MergeCursor(new Cursor[] {oldData, data});
incomeAdapter.swapCursor(merge);
}
else {
Log.v("Income Activity", "no current adapter");
incomeAdapter.swapCursor(data);
}
throws an exception:
07-13 16:56:15.455: E/AndroidRuntime(5665): FATAL EXCEPTION: main
07-13 16:56:15.455: E/AndroidRuntime(5665): android.database.StaleDataException:
Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated
prior to calling this method.
EDIT2:
As per request, the code where data is requested:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
orderBy = sortOrder + " LIMIT " + limitSkip + "," + limitCount;
CursorLoader cursorLoader = new CursorLoader(this,
DatabaseProvider.CONTENT_URI1, PROJECTION, null, null, orderBy);
return cursorLoader;
}
You need to use the previous cursor: incomeAdapter.getCursor() and merge it with the new data.
MergeCursor comes to your aid there:
Cursor oldData = incomeAdapter.getCursor();
MergeCursor merge = new MergeCursor(new Cursor[] {oldData, data});
incomeAdapter.swapCursor(merge);
EDIT:
You are using a CursorLoader. You don't need to re-query the database or LIMIT your queries. The loader does all that for you when specific data is requested.
Android Docs: Loaders
I am trying to get values of rows from requested columns using cursor but I am not getting that how to do it,what is the index inside cursor.getLong(?) ,Here is my code it works but I don't how is it working? Please help.
private Message cursorToMessage(Cursor cursor) {
Message message = new Message();
message.setId(cursor.getLong(0));
message.setmessage(cursor.getString(1));
message.setthreadid(cursor.getLong(0));
return message;
}
the index is the position of the column in your projection of the query.
do if you have in your projection that you want columns id,message,threadid then you would do
long id = cursor.getLong(0);
String message = cursor.getString(1);
long threadId = cursor.getLong(2);
the proper way to get a column so that you dont mix the indexes up would be to do this
cursor.getLong(cursor.getColumnIndex("id"));
I dont know if you did this or not but you should also be checking if the cursor has anything in it.
if(cursor != null && cursor.moveToFirst()){
message.setId(cursor.getLong(0));
message.setmessage(cursor.getString(1));
message.setthreadid(cursor.getLong(0));
}
I'm developing a small application so I can get more experience of Android development. I wrote a method that list all of contact number and total messages (just like GoSMS or default SMS Application). But problem that I'm facing right now is slow performance. Below is what I have done to get the result.
Sample result: Mr AAA (10000)
Steps:
Get all SMS thread ID
Loop and get total messages belong to each thread ID.
Get the contact number belong to that thread.
Use that number and PhoneLookup to get contact name.
Here is the method:
public void populateContactList()
{
// Get all sms threads
Cursor smsAddressCursor = getContentResolver().query(
SMSCVar.CONTENT_URI,
new String[] { "DISTINCT "+SMSCVar.THREAD_ID},
null,
null,
null);
while(smsAddressCursor.moveToNext())
{
Contact c = new Contact();
// Get thread_id
String thread_id = smsAddressCursor.getString(smsAddressCursor.getColumnIndex(SMSCVar.THREAD_ID));
// Get total messages
Cursor totalMessage = getContentResolver().query(
SMSCVar.CONTENT_URI,
new String[] {"count("+SMSCVar.BODY+")"},
SMSCVar.THREAD_ID+" = ?",
new String[] {thread_id},
null);
totalMessage.moveToNext();
c.setNumberOfSMS(totalMessage.getInt(0));
totalMessage.close();
// Get number
Cursor number = getContentResolver().query(
SMSCVar.CONTENT_URI,
new String[] {SMSCVar.ADDRESS},
SMSCVar.THREAD_ID+" = ?",
new String[] {thread_id},
null);
number.moveToNext();
String pNumber = number.getString(0);
number.close();
// Get contact name
Uri uriPhonenumber = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI
, Uri.encode(pNumber));
Cursor contactDisplayName = getContentResolver().query(
uriPhonenumber,
new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME},
null,
null,
null);
// If cursor is not null and has at least one result
if(!contactDisplayName.isNull(0) && contactDisplayName.getCount() > 0)
{
// Get contact name for display
contactDisplayName.moveToNext();
c.setContactName(contactDisplayName
.getString(contactDisplayName
.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME)));
}
else
{
// Get contact number for display
c.setContactName(pNumber);
}
// Don't get confuse here, setContactNumber method is not used for display purpose.
c.setContactNumber(pNumber);
contactListAdapter.add(c);
contactDisplayName.close();
}
smsAddressCursor.close();
}
I've coded to close cursor properly but I still get GARBAGE COLLECTOR messages and slow retrieving time (5 seconds). My SMS is over 11000 messages.
So please assist me!
Thank you!
P/S: I'm not an English native speaker so I've tried my best to make my question easy to understand.