Commit cdb00054 authored by Timur Kristóf's avatar Timur Kristóf

[libcontacts] Use libphonenumber for number matching. Contributes to JB#38835

This commit yields a significant improvement to how our phone number
matching works. It gets rid of a lot of false positive matches.

Previously we used a "minimized" number which we stored in the cache
and used for matching. This lead to false positives between numbers
for which the "minimized" number was the same when the full wasn't,
for example when the area code of the number differs in only one digit.

Examples of solved false positive matches:
"+36 20 123 4567" and "+36 30 123 4567" were matched incorrectly.
"+36 20 123 4567" and "+44 20 123 4567" were matched incorrectly.

The new approach is to cache the full phone number and perform the
matching between the full numbers using libphonenumber, which has
a quite resilient algorithm for this. Local numbers and
international direct dial (IDD) are still matched:

Examples of local and IDD numbers that still work:
"+36 20 123 4567" still matches "06 20 123 4567" (local)
"+36 20 123 4567" still matches "00 36 70 381 0581" (IID)

Limitations:
We currently do NOT pass a default region code to libphonenumber,
which means that some false positives can still occour in edge
cases when a country code would be required to distinguish between
the two numbers. This only happens when some numbers are supplied
in a non-international format.

Examples of remaining false positives:
"06 20 123 4567" is a false positive match of "+1 0620 123 4567"
"00 36 20 123 4567" is a false positive match of "+1 00 36 20 123 4567"

We deem that the remaining false positives are unlikely enough that
they don't matter in practice. If this becomes relevant, we will need
to make it possible to pass a region code to the matching algorithm.
parent 2df678a8
......@@ -15,6 +15,7 @@ BuildRequires: pkgconfig(mlite5)
BuildRequires: pkgconfig(mlocale5)
BuildRequires: pkgconfig(mce)
BuildRequires: pkgconfig(qtcontacts-sqlite-qt5-extensions) >= 0.2.31
BuildRequires: libphonenumber-devel
BuildRequires: qt5-qttools
BuildRequires: qt5-qttools-linguist
......
......@@ -73,6 +73,7 @@
#include <mce/dbus-names.h>
#include <mce/mode-names.h>
#include <phonenumbers/phonenumberutil.h>
QTVERSIT_USE_NAMESPACE
......@@ -3313,41 +3314,66 @@ void SeasideCache::resolveAddress(ResolveListener *listener, const QString &firs
SeasideCache::CacheItem *SeasideCache::itemMatchingPhoneNumber(const QString &number, const QString &normalized, bool requireComplete)
{
QMultiHash<QString, CachedPhoneNumber>::const_iterator it = m_phoneNumberIds.find(number), end = m_phoneNumberIds.constEnd();
if (it != end) {
// How many matches are there for this number?
int matchCount = 1;
QMultiHash<QString, CachedPhoneNumber>::const_iterator matchingIt = it + 1;
while ((matchingIt != end) && (matchingIt.key() == number)) {
++matchCount;
++matchingIt;
}
if (matchCount == 1)
return itemById(it->iid, requireComplete);
// Choose the best match from these contacts
int bestMatchLength = 0;
CacheItem *matchItem = 0;
for ( ; matchCount > 0; ++it, --matchCount) {
if (CacheItem *item = existingItem(it->iid)) {
int matchLength = bestPhoneNumberMatchLength(item->contact, normalized);
if (matchLength > bestMatchLength) {
bestMatchLength = matchLength;
matchItem = item;
if (bestMatchLength == ExactMatch)
break;
}
}
if (it == end)
return 0;
QHash<QString, quint32> possibleMatches;
::i18n::phonenumbers::PhoneNumberUtil *util = ::i18n::phonenumbers::PhoneNumberUtil::GetInstance();
::std::string normalizedStdStr = normalized.toStdString();
for (QMultiHash<QString, CachedPhoneNumber>::const_iterator matchingIt = it;
matchingIt != end && matchingIt.key() == number;
++matchingIt) {
const CachedPhoneNumber &cachedPhoneNumber = matchingIt.value();
// Bypass libphonenumber if the numbers match exactly
if (matchingIt->normalizedNumber == normalized)
return itemById(cachedPhoneNumber.iid, requireComplete);
::i18n::phonenumbers::PhoneNumberUtil::MatchType matchType =
util->IsNumberMatchWithTwoStrings(normalizedStdStr, cachedPhoneNumber.normalizedNumber.toStdString());
switch (matchType) {
case ::i18n::phonenumbers::PhoneNumberUtil::EXACT_MATCH:
// This is the optimal outcome
return itemById(cachedPhoneNumber.iid, requireComplete);
case ::i18n::phonenumbers::PhoneNumberUtil::NSN_MATCH:
case ::i18n::phonenumbers::PhoneNumberUtil::SHORT_NSN_MATCH:
// Store numbers whose NSN (national significant number) might match
// Example: if +36701234567 is calling, then 1234567 is an NSN match
possibleMatches.insert(cachedPhoneNumber.normalizedNumber, cachedPhoneNumber.iid);
break;
default:
// Either couldn't parse the number or it was NO_MATCH, ignore it
break;
}
}
if (matchItem != 0) {
if (requireComplete) {
ensureCompletion(matchItem);
// Choose the best match from these contacts
int bestMatchLength = 0;
CacheItem *matchItem = 0;
for (QHash<QString, quint32>::const_iterator matchingIt = possibleMatches.begin(); matchingIt != possibleMatches.end(); ++matchingIt) {
if (CacheItem *item = existingItem(*matchingIt)) {
int matchLength = bestPhoneNumberMatchLength(item->contact, normalized);
if (matchLength > bestMatchLength) {
bestMatchLength = matchLength;
matchItem = item;
if (bestMatchLength == ExactMatch)
break;
}
return matchItem;
}
}
if (matchItem != 0) {
if (requireComplete) {
ensureCompletion(matchItem);
}
return matchItem;
}
return 0;
}
int SeasideCache::contactIndex(quint32 iid, FilterType filterType)
......
......@@ -22,6 +22,7 @@ packagesExist(mlite5) {
warning("mlite not available. Some functionality may not work as expected.")
}
PKGCONFIG += mlocale5 mce qtcontacts-sqlite-qt5-extensions
LIBS += -lphonenumber
DEFINES += CONTACTCACHE_BUILD
......
......@@ -4,6 +4,7 @@ TARGET = tst_resolve
QT += contacts-private dbus
PKGCONFIG += mlocale5
LIBS += -lphonenumber
# We need the moc output for ContactManagerEngine from sqlite-extensions
extensionsIncludePath = $$system(pkg-config --cflags-only-I qtcontacts-sqlite-qt5-extensions)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment