Commit 06adaa51 authored by Venemo's avatar Venemo

Merge branch 'phonenumbermatch-jb38835' into 'master'

More robust phone number matching using libphonenumber

See merge request mer-core/libcontacts!15
parents cdf40120 2f6013cc
......@@ -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
......@@ -2209,8 +2210,10 @@ bool SeasideCache::updateContactIndexing(const QContact &oldContact, const QCont
resolveUnknownAddresses(address.first, address.second, item);
}
if (!m_phoneNumberIds.contains(address.second, iid))
m_phoneNumberIds.insert(address.second, iid);
CachedPhoneNumber cachedPhoneNumber(normalizePhoneNumber(phoneNumber.number()), iid);
if (!m_phoneNumberIds.contains(address.second, cachedPhoneNumber))
m_phoneNumberIds.insert(address.second, cachedPhoneNumber);
}
}
......@@ -2218,7 +2221,7 @@ bool SeasideCache::updateContactIndexing(const QContact &oldContact, const QCont
if (!oldAddresses.isEmpty()) {
modified = true;
foreach (const StringPair &address, oldAddresses) {
m_phoneNumberIds.remove(address.second, iid);
m_phoneNumberIds.remove(address.second);
}
oldAddresses.clear();
}
......@@ -2886,7 +2889,8 @@ void SeasideCache::addressRequestStateChanged(QContactAbstractRequest::State sta
const QList<QContact> &resolvedContacts = it.key()->contacts();
if (!resolvedContacts.isEmpty()) {
if (resolvedContacts.count() == 1) {
if (resolvedContacts.count() == 1 && data.first != QString()) {
// Return the result because it is the only resolved contact; however still filter out false positive phone number matches
item = itemById(apiId(resolvedContacts.first()), false);
} else {
// Lookup the result in our updated indexes
......@@ -3309,42 +3313,67 @@ void SeasideCache::resolveAddress(ResolveListener *listener, const QString &firs
SeasideCache::CacheItem *SeasideCache::itemMatchingPhoneNumber(const QString &number, const QString &normalized, bool requireComplete)
{
QMultiHash<QString, quint32>::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, quint32>::const_iterator matchingIt = it + 1;
while ((matchingIt != end) && (matchingIt.key() == number)) {
++matchCount;
++matchingIt;
}
if (matchCount == 1)
return itemById(*it, requireComplete);
// Choose the best match from these contacts
int bestMatchLength = 0;
CacheItem *matchItem = 0;
for ( ; matchCount > 0; ++it, --matchCount) {
if (CacheItem *item = existingItem(*it)) {
int matchLength = bestPhoneNumberMatchLength(item->contact, normalized);
if (matchLength > bestMatchLength) {
bestMatchLength = matchLength;
matchItem = item;
if (bestMatchLength == ExactMatch)
break;
}
}
QMultiHash<QString, CachedPhoneNumber>::const_iterator it = m_phoneNumberIds.find(number), end = m_phoneNumberIds.constEnd();
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)
......
......@@ -139,6 +139,21 @@ public:
void *key;
};
struct CachedPhoneNumber
{
CachedPhoneNumber() {}
CachedPhoneNumber(const QString &n, quint32 i) : normalizedNumber(n), iid(i) {}
CachedPhoneNumber(const CachedPhoneNumber &other) : normalizedNumber(other.normalizedNumber), iid(other.iid) {}
bool operator==(const CachedPhoneNumber &other) const
{
return other.normalizedNumber == normalizedNumber && other.iid == iid;
}
QString normalizedNumber;
quint32 iid;
};
struct CacheItem
{
CacheItem() : itemData(0), iid(0), statusFlags(0), contactState(ContactAbsent), listeners(0), filterMatchRole(-1) {}
......@@ -422,7 +437,7 @@ private:
QBasicTimer m_expiryTimer;
QBasicTimer m_fetchTimer;
QHash<quint32, CacheItem> m_people;
QMultiHash<QString, quint32> m_phoneNumberIds;
QMultiHash<QString, CachedPhoneNumber> m_phoneNumberIds;
QHash<QString, quint32> m_emailAddressIds;
QHash<QPair<QString, QString>, quint32> m_onlineAccountIds;
QHash<QContactId, QContact> m_contactsToSave;
......
......@@ -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
......
......@@ -60,7 +60,9 @@ private:
QList<QContactId> m_createdContacts;
private slots:
void resolveByPhone_data();
void resolveByPhone();
void resolveByPhoneNotFound_data();
void resolveByPhoneNotFound();
void resolveByEmail();
void resolveByEmailNotFound();
......@@ -145,13 +147,51 @@ void tst_Resolve::makeContacts()
QVERIFY(makeContact("Daffy", "Duck", "+358470009955", "daffyd@example.com", ""));
QVERIFY(makeContact("Dafferd", "Duck", "", "daffy.d@example.com", ""));
QVERIFY(makeContact("Ernest", "Everest", "+358477758885", "", ""));
QVERIFY(makeContact("John", "Smith", "+36701234567", "", ""));
QVERIFY(makeContact("Jane", "Smith", "06207654321", "", ""));
}
void tst_Resolve::resolveByPhone_data()
{
QTest::addColumn<QString>("country");
QTest::addColumn<QString>("number");
QTest::addColumn<QString>("matchingName");
QTest::newRow("Daffy international")
<< "FI" << "+358470009955" << "Daffy";
QTest::newRow("Daffy international direct dial")
<< "FI" << "00358470009955" << "Daffy";
QTest::newRow("John international")
<< "HU" << "+36701234567" << "John";
QTest::newRow("John international direct dial")
<< "HU" << "0036701234567" << "John";
QTest::newRow("John international without plus")
<< "HU" << "36701234567" << "John";
QTest::newRow("John full national")
<< "HU" << "06701234567" << "John";
QTest::newRow("Jane international")
<< "HU" << "+36207654321" << "Jane";
QTest::newRow("Jane international without plus")
<< "HU" << "36207654321" << "Jane";
QTest::newRow("Jane full national")
<< "HU" << "06207654321" << "Jane";
}
void tst_Resolve::resolveByPhone()
{
// Note:
// When running these tests, make sure you don't have any of the above
// numbers in your contact list, as those will interfere with the results.
QFETCH(QString, country);
QFETCH(QString, number);
QFETCH(QString, matchingName);
Q_UNUSED(country)
SeasideCache::CacheItem *item;
TestResolveListener listener;
QString number("+358470009955");
item = SeasideCache::resolvePhoneNumber(&listener, number, true);
if (!item) {
......@@ -159,20 +199,57 @@ void tst_Resolve::resolveByPhone()
item = listener.m_item;
}
QVERIFY(item != 0);
QContactName name = item->contact.detail<QContactName>();
QCOMPARE(name.firstName(), QString::fromLatin1("Daffy"));
QCOMPARE(name.firstName(), matchingName);
}
void tst_Resolve::resolveByPhoneNotFound_data()
{
QTest::addColumn<QString>("country");
QTest::addColumn<QString>("number");
QTest::newRow("Non-existent British number")
<< "GB" << "+44123456789";
QTest::newRow("Non-existent Finnish number")
<< "FI" << "+35898765432100";
QTest::newRow("Daffy same NSN different country")
<< "HU" << "+36470009955";
QTest::newRow("Daffy same number same country different area code")
<< "FI" << "+358570009955";
QTest::newRow("John same NSN different country")
<< "HU" << "+44701234567";
QTest::newRow("John same number same country different area code")
<< "HU" << "+36201234567";
QTest::newRow("Jane same number different area code international")
<< "HU" << "+36307654321";
QTest::newRow("Jane same number different area code full national")
<< "HU" << "06307654321";
}
void tst_Resolve::resolveByPhoneNotFound()
{
// Note:
// When running these tests, make sure you don't have any of the above
// numbers in your contact list, as those will interfere with the results.
QFETCH(QString, country);
QFETCH(QString, number);
Q_UNUSED(country)
SeasideCache::CacheItem *item;
TestResolveListener listener;
QString number("+358470000000");
item = SeasideCache::resolvePhoneNumber(&listener, number, true);
if (!item) {
QTRY_VERIFY(listener.m_resolved);
item = listener.m_item;
} else {
qWarning() << "should not have resolved:" << number << "to" << item->displayLabel;
}
QCOMPARE(item, (SeasideCache::CacheItem *)0);
......
......@@ -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