Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New API for specialized matching of phone numbers (PhoneLookup) #259

Closed
5 tasks done
vestrel00 opened this issue Sep 4, 2022 · 16 comments
Closed
5 tasks done

New API for specialized matching of phone numbers (PhoneLookup) #259

vestrel00 opened this issue Sep 4, 2022 · 16 comments
Assignees
Labels
enhancement New feature or request

Comments

@vestrel00
Copy link
Owner

vestrel00 commented Sep 4, 2022

Problem

As @mianaliasjad pointed out in #258 (comment), the BroadQuery API currently does not match phone numbers that uses a country code (e.g. "+923123456789") when a search string is provided that does not use the country code (e.g. 03123456789).

The following will not return the contact;

val contact = Contacts(context)
        .broadQueryWithPermission()
        .match(BroadQuery.Match.PHONE)
        .wherePartiallyMatches( "03123456789" )
        .limit(1)
        .find()
        .firstOrNull()

This is due to (intentionally) NOT using ContactsContract.PhoneLookup.CONTENT_FILTER_URI for the matching process because it's purpose is to emulate the behavior of the other major Contacts app, which it is doing.

More info in the discussion #258

Solution

Implement a new API, which I might call PhoneLookup (name not final), that uses ContactsContract.PhoneLookup.CONTENT_FILTER_URI for the matching process.

  • Docs page
  • Add usages to cheatsheet
  • Implement permissions extensions
  • Implement async extensions
  • Update gh-pages
@vestrel00 vestrel00 added the enhancement New feature or request label Sep 4, 2022
@vestrel00 vestrel00 self-assigned this Sep 4, 2022
@vestrel00 vestrel00 changed the title Match contacts with phone numbers that use country codes using a search string that does not use the country code New API for specialized matching of phone numbers Sep 4, 2022
@vestrel00 vestrel00 changed the title New API for specialized matching of phone numbers New API for specialized matching of phone numbers (PhoneLookup) Sep 4, 2022
@mianaliasjad
Copy link

eagerly waiting for this feature :D

@vestrel00 vestrel00 pinned this issue Sep 6, 2022
@vestrel00
Copy link
Owner Author

vestrel00 commented Sep 6, 2022

I'm starting to look at this. It seems like the ContactsContract.PhoneLookup has its own table;

public static final class PhoneLookup implements BaseColumns, PhoneLookupColumns,
            ContactsColumns, ContactOptionsColumns, ContactNameColumns 

It is essentially an index that has a lot of the same columns as the Data table, which also has joins on ContactsColumns, ContactOptionsColumns, ContactNameColumns. The interesting part is PhoneLookupColumns;

    protected interface PhoneLookupColumns {
        public static final String DATA_ID = "data_id";
        public static final String CONTACT_ID = "contact_id";
        public static final String NUMBER = "number";
        public static final String TYPE = "type";
        public static final String LABEL = "label";
        public static final String NORMALIZED_NUMBER = "normalized_number";
    }

Apart from NORMALIZED_NUMBER, all of these column names are the same in the Data table.

Just like Data table queries, this means that results may contain the same CONTACT_ID multiple times. Therefore, the result of this new API library can be either a list of;

  1. New entity type that includes only Contacts columns + PhoneLookupColumns; excluding RawContact info because it is not accessible from this table.
  2. Existing Contact entity.

If we go with option 1, then each contact may appear multiple times in the results but with different values for TYPE, LABEL, and NORMALIZED_NUMBER. This may be the desired behavior for some use cases. For example;

  • contact X (phone # 123)
  • contact Y (phone # 124)
  • contact X (phone # 125)

If we go with option 2, then each contact will appear only once in the results and will contain aggregate phone numbers and other data. For example;

  • contact X (phone # 123) (phone # 125)
  • contact Y (phone # 124)

Option 2 is pretty much just like BroadQuery and Query except the matching algorithm is different. Option 1 is quite different and will require a LOT more new code.

For now, I will go with option 2.

UPDATE: It also seems like option 2 is a better option regardless because the PhoneLookup query can return the same row twice, resulting in duplicates if option 1 is taken. For example, the phone number "012345678900" will appear twice in the results even though there is only one occurrence of that number in the Data table.

UPDATE 2: This only matches numbers EXACTLY. No partial matching. Therefore, only duplicate numbers from 1 or more contacts will yield more than one result. This makes option 1 even less "important".

@vestrel00
Copy link
Owner Author

Screen Shot 2022-09-06 at 7 37 07 AM
Screen Shot 2022-09-06 at 7 37 43 AM

Wow... It seems like I can't even reliably get a reference to the Contacts for API versions lower than 24...

Hmmm... I have to think about this more 😓

@mianaliasjad
Copy link

Screen Shot 2022-09-06 at 7 37 07 AM Screen Shot 2022-09-06 at 7 37 43 AM

Wow... It seems like I can't even reliably get a reference to the Contacts for API versions lower than 24...

Hmmm... I have to think about this more 😓

That's sad. What about lookup key? Can you get that and search with that.

@vestrel00
Copy link
Owner Author

vestrel00 commented Sep 6, 2022

That's sad. What about lookup key? Can you get that and search with that.

Yes! I actually just realized this as I was trying to look for an alternative.

Also, I just found out that thePhoneLookup._ID (BaseColumns._ID) in the PhoneLookup table is the Contact ID even for API versions below 24. I tested this by adding several different numbers for the same contact. Each of the different numbers in the PhoneLookup table contained the same Contact ID.

Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?

I'll take a risk and just use the PhoneLookup._ID. Using long lookup keys is less performant than an integer.

@vestrel00
Copy link
Owner Author

Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?

Maybe the BaseColumns._ID for the PhoneLookup table might be used for something else in the future? I verified that from API 19 to 33 that it is still the Contact ID. I also checked on a physical Samsung device.

But... Just to be safe + future proofing, I'll do the following;

if (API >= 24) PhoneLookup.CONTACT_ID else PhoneLookup._ID

@vestrel00
Copy link
Owner Author

Something I've realized. The PhoneLookup only matches numbers that match the search string EXACTLY.

So, if we have contacts with the following numbers;

  • 123
  • 1234
  • 1234
  • 12345
  • 12345678900
  • 12345678911

Searching for "123" will only return the one contact with the number "123".

Searching for "1234" will return the contact(s) with the number "1234".

I'll add this to the API documentation!

@vestrel00
Copy link
Owner Author

Okay. I should have all the information I need to implement this! Research complete!

I'll also add support for ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS (available for API 21+).

@vestrel00
Copy link
Owner Author

@mianaliasjad, this issue is done! It will be included in the next release, 0.2.4, which I'm aiming to publish some time this week 🔥

Full documentation is already available at https://vestrel00.github.io/contacts-android/basics/query-contacts-by-phone-or-sip/

It looks something like this;

val contacts = Contacts(context)
    .phoneLookupQuery()
    .whereExactlyMatches("03123456789")
    .find()

@vestrel00 vestrel00 unpinned this issue Sep 6, 2022
vestrel00 added a commit that referenced this issue Sep 6, 2022
@mianaliasjad
Copy link

That's sad. What about lookup key? Can you get that and search with that.

Yes! I actually just realized this as I was trying to look for an alternative.

Also, I just found out that thePhoneLookup._ID (BaseColumns._ID) in the PhoneLookup table is the Contact ID even for API versions below 24. I tested this by adding several different numbers for the same contact. Each of the different numbers in the PhoneLookup table contained the same Contact ID.

Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?

I'll take a risk and just use the PhoneLookup._ID. Using long lookup keys is less performant than an integer.

I am not sure about that. I shall test it for 🙂

@mianaliasjad
Copy link

Something I've realized. The PhoneLookup only matches numbers that match the search string EXACTLY.

So, if we have contacts with the following numbers;

  • 123
  • 1234
  • 1234
  • 12345
  • 12345678900
  • 12345678911

Searching for "123" will only return the one contact with the number "123".

Searching for "1234" will return the contact(s) with the number "1234".

I'll add this to the API documentation!

Exactly that's what we try to achieve while searching for the name of incoming call. We need exact result.

@vestrel00
Copy link
Owner Author

That's sad. What about lookup key? Can you get that and search with that.

Yes! I actually just realized this as I was trying to look for an alternative.

Also, I just found out that thePhoneLookup._ID (BaseColumns._ID) in the PhoneLookup table is the Contact ID even for API versions below 24. I tested this by adding several different numbers for the same contact. Each of the different numbers in the PhoneLookup table contained the same Contact ID.

Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?

I'll take a risk and just use the PhoneLookup._ID. Using long lookup keys is less performant than an integer.

I am not sure about that. I shall test it for 🙂

Sure. Please double check me =)

@mianaliasjad
Copy link

That's sad. What about lookup key? Can you get that and search with that.

Yes! I actually just realized this as I was trying to look for an alternative.

Also, I just found out that thePhoneLookup._ID (BaseColumns._ID) in the PhoneLookup table is the Contact ID even for API versions below 24. I tested this by adding several different numbers for the same contact. Each of the different numbers in the PhoneLookup table contained the same Contact ID.

Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?

I'll take a risk and just use the PhoneLookup._ID. Using long lookup keys is less performant than an integer.

I just read the documentation and you are correct. The coulmn _ID and CONTACT_ID are same and they are coming from Contacts table by using a Join in the query. Both of them represent the same unique row of Contacts table.

Check here .

Screenshot_20220907-044800_Chrome

@vestrel00
Copy link
Owner Author

vestrel00 commented Sep 6, 2022

That's sad. What about lookup key? Can you get that and search with that.

Yes! I actually just realized this as I was trying to look for an alternative.
Also, I just found out that thePhoneLookup._ID (BaseColumns._ID) in the PhoneLookup table is the Contact ID even for API versions below 24. I tested this by adding several different numbers for the same contact. Each of the different numbers in the PhoneLookup table contained the same Contact ID.
Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?
I'll take a risk and just use the PhoneLookup._ID. Using long lookup keys is less performant than an integer.

I just read the documentation and you are correct. The coulmn _ID and CONTACT_ID are same and they are coming from Contacts table by using a Join in the query. Both of them represent the same unique row of Contacts table.

Check here .

I'm a bit embarrassed for being caught not checking the official documentation. I just assumed there would be no documentation around the _ID because MOST of the work I've done building this library involved doing hands-on investigations due to lack of official documentation. So, I did not bother looking for docs around this and just observed the behavior on different API versions and devices.

@mianaliasjad
Copy link

That's sad. What about lookup key? Can you get that and search with that.

Yes! I actually just realized this as I was trying to look for an alternative.
Also, I just found out that thePhoneLookup._ID (BaseColumns._ID) in the PhoneLookup table is the Contact ID even for API versions below 24. I tested this by adding several different numbers for the same contact. Each of the different numbers in the PhoneLookup table contained the same Contact ID.
Makes me wonder why they added PhoneLookup.CONTACT_ID in API 24. Maybe just for clarity?
I'll take a risk and just use the PhoneLookup._ID. Using long lookup keys is less performant than an integer.

I just read the documentation and you are correct. The coulmn _ID and CONTACT_ID are same and they are coming from Contacts table by using a Join in the query. Both of them represent the same unique row of Contacts table.
Check here .

I'm a bit embarrassed for being caught not checking the official documentation. I just assumed there would be no documentation around the _ID because MOST of the work I've done building this library involved doing hands-on investigations due to lack of official documentation. So, I did not bother looking for docs around this and just observed the behavior on different API versions and devices.

You should not be embarrassed 😅 because in android documentation says something else and on device functionality is something else mostly 🤣

So yeah you have done actual on device testing. Now we are 100% sure that those two are same thing 😉

@vestrel00
Copy link
Owner Author

This is included in 0.2.4!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants