Speed up ContactResolver by removing looping

The previous contactresolver was O(E*A) where E was the number of commhistory events and A was the number of addresses in the contacts db. (Technically something like O(E*A*n) where n is the average number of addresses per contact, but n is usually a small number)

I rewrote its core loop so that it canonicalizes the addresses, which allows hashtable lookups instead of list scans. From the commit message:

- Canonicalize requested addresses so that they can be looked up in hashes (using a canonicalization that matches what addressMatchesList used to do)

- Fill in all the resolved contacts when the events list is sent out, instead of looping over all the events whenever something changes.

- Side effect: having lots of duplicate addresses in the events list no longer slows down contactUpdated processing

The benefit is visible in voicecall-ui startup, which in tests on iamer's phone showed improvement from 40s of cpu time to 12s of cpu time. He has about 8000 commhistory events and about 500 contacts.
FWIW:
```
#include <QHash>
#include <QString>
#include <QElapsedTimer>
#include <QtDebug>

template<typename T>
struct HashMemo {
    T value;
    mutable uint hash;
    mutable uint seed;

    HashMemo(const T &v) : value(v), hash(0), seed(-1) {}
};

template<typename T>
bool operator==(const HashMemo<T> &lhs, const HashMemo<T> &rhs) { return lhs.value == rhs.value; }

template<typename T>
uint qHash(const HashMemo<T>& m, uint seed = 0) {
    if (m.hash == 0 || m.seed == -1) {
        m.seed = seed;
        m.hash = qHash(m.value, seed);
    }
    return m.hash;
}

int main(int, char**) {
    const int n = 100 * 1000, m = 50;

    qsrand(0);

    QList<QString> l;
    l.reserve(n);
    for (int i = 0; i < n; ++i) {
        QString s;
        s.reserve(m);
        for (int j = 0; j < m; ++j) {
            s.append(QChar('a' + qrand() % 26));
        }
        l.append(s);
    }

    QHash<QString, int> h1;
    QHash<HashMemo<QString>, int> h2;
    int x = 0;
    QElapsedTimer t; t.start();

    foreach (const QString &s, l) { h1.insert(s, 1); }
    qDebug() << "h1 insert:" << t.restart();
    foreach (const QString &s, l) { if (h1.contains(s)) { x += h1.value(s); } }
    qDebug() << "h1 search:" << t.restart();

    foreach (const HashMemo<QString> &s, l) { h2.insert(s, 1); }
    qDebug() << "h2 insert:" << t.restart();
    foreach (const HashMemo<QString> &s, l) { if (h2.contains(s)) { x += h2.value(s); } }
    qDebug() << "h2 search:" << t.restart();
    return 0;
}
```