8

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.

brandall
  • 6,094
  • 4
  • 49
  • 103

4 Answers4

4

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

clearlight
  • 12,255
  • 11
  • 57
  • 75
marmor
  • 27,641
  • 11
  • 107
  • 150
  • Thanks for your answer - I appreciate that `there isn't a one-to-one link between sms and contacts`, but one is 'created' somewhere on receipt of the sms/mms, which results in 'an id/ids' being populated. From your link I've tried to find in the Android source code where, from the address(es), this mapping takes place, but with no joy. I wonder if it is now down to the default sms/mms provider to implement this? Do you have any experience of this please? – brandall Jan 08 '17 at 19:09
  • I think I might have found it [here](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/5.1.1_r1/com/android/mms/data/Contact.java#343). It suggests it uses `ContactsContract.CommonDataKinds.Phone`? I swear I already checked this id didn't match. – brandall Jan 08 '17 at 19:23
  • @brandall a contact can be deleted/created/changed, and there can be multiple different contacts with the same number, the sms db can't keep up with changes to the contacts db, the only thing that can't change is the canonical address that sent/received that sms, that is why the RecipientId must map to a phone(s) not to a contact. The rest is, as implemented in mms.data.Contact.java, up to the implementor to map between canonical-address and contact – marmor Jan 09 '17 at 07:34
  • I hear you, but the system is somewhere creating that mapping between the canonical address and the `Person`, whenever possible. This is what I'm after, as when I find it, I can reverse the process. The `Phone` id doesn't match from the comment above, neither does the `Contacts` id referenced from [here](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/5.1.1_r1/com/android/mms/data/Contact.java#985). I'm currently rerunning queries on all of the id fields referenced in `ContactsContract' to make sure I didn't overlook one first time around. – brandall Jan 10 '17 at 09:35
1

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.

rds
  • 26,253
  • 19
  • 107
  • 134
0

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.

-1

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.

Imran Aslam
  • 208
  • 2
  • 15
  • 1
    The question is asking why the returned value of `PERSON` relates to the deprecated `Contacts.People` id, rather than using a value from `ContactsContract` tables. Querying the message table is not the problem. – brandall Jan 06 '17 at 11:28