Example #1
0
void TestSimPlugin::testEmpty()
{
    QContactManager &m(m_controller->contactManager());

    // Add the Forrest Gump contact manually
    QContact forrest;

    QContactNickname n;
    n.setNickname("Forrest Gump");
    forrest.saveDetail(&n);

    QContactSyncTarget st;
    st.setSyncTarget(QStringLiteral("sim-test"));
    forrest.saveDetail(&st);

    QVERIFY(m.saveContact(&forrest));

    QCOMPARE(getAllSimContacts(m).count(), 1);
    QCOMPARE(m_controller->busy(), false);

    // Process an empty VCard
    m_controller->simPresenceChanged(true);
    m_controller->vcardDataAvailable(QString());
    QCOMPARE(m_controller->busy(), true);

    QTRY_VERIFY(m_controller->busy() == false);

    // No-longer-present contact should be removed
    QList<QContact> simContacts(getAllSimContacts(m));
    QCOMPARE(simContacts.count(), 0);
}
Example #2
0
void TestSimPlugin::testAddAndRemove()
{
    QContactManager &m(m_controller->contactManager());

    // Add the Forrest Gump contact manually
    QContact forrest;

    QContactNickname n;
    n.setNickname("Forrest Gump");
    forrest.saveDetail(&n);

    QContactSyncTarget st;
    st.setSyncTarget(QStringLiteral("sim-test"));
    forrest.saveDetail(&st);

    QVERIFY(m.saveContact(&forrest));

    QCOMPARE(getAllSimContacts(m).count(), 1);
    QCOMPARE(m_controller->busy(), false);

    // Process a VCard set not containing Forrest Gump but another contact
    m_controller->simPresenceChanged(true);
    m_controller->vcardDataAvailable(QStringLiteral(
"BEGIN:VCARD\n"
"VERSION:3.0\n"
"FN:Forrest Whittaker\n"
"TEL;TYPE=HOME,VOICE:(404) 555-1234\n"
"END:VCARD\n"));
    QTRY_VERIFY(m_controller->busy() == false);

    // Forrest Gump should be removed, Forrest Whittaker added
    QList<QContact> simContacts(getAllSimContacts(m));
    QCOMPARE(simContacts.count(), 1);
    QCOMPARE(simContacts.at(0).detail<QContactNickname>().nickname(), QStringLiteral("Forrest Whittaker"));
}
void SeasidePerson::setNickname(const QString &name)
{
    QContactNickname nameDetail = mContact.detail<QContactNickname>();
    nameDetail.setNickname(name);
    mContact.saveDetail(&nameDetail);
    emit nicknameChanged();
    recalculateDisplayLabel();
}
void
ut_qtcontacts_trackerplugin_common::setTestNicknameToContact(QContact &contact,
                                                             const QString &id) const
{
    QContactNickname nicknameDetail;
    const QString nickname = makeUniqueName(id);
    nicknameDetail.setNickname(nickname);
    QVERIFY(contact.saveDetail(&nicknameDetail));
}
Example #5
0
QContactDetail *CntTransformNickname::transformItemField(const CContactItemField& field, const QContact &contact)
{
	QContactNickname *name = new QContactNickname(contact.detail<QContactNickname>());

	CContactTextField* storage = field.TextStorage();
	QString nameValue = QString::fromUtf16(storage->Text().Ptr(), storage->Text().Length());

	for (int i = 0; i < field.ContentType().FieldTypeCount(); i++)
	{
		//Prefix
		if (field.ContentType().FieldType(i) == KUidContactFieldSecondName)
		{
			name->setNickname(nameValue);
		}
	}

	return name;
}
void TestSimPlugin::testClear()
{
    QContactManager &m(m_controller->contactManager());

    // Add two contacts manually
    QContact gump;

    QContactNickname n;
    n.setNickname("Forrest Gump");
    gump.saveDetail(&n);

    QContactSyncTarget st;
    st.setSyncTarget(QStringLiteral("sim-test"));
    gump.saveDetail(&st);

    QContact whittaker;

    n.setNickname("Forrest Whittaker");
    whittaker.saveDetail(&n);

    st.setSyncTarget(QStringLiteral("sim-test"));
    whittaker.saveDetail(&st);

    QVERIFY(m.saveContact(&gump));
    QVERIFY(m.saveContact(&whittaker));

    QCOMPARE(getAllSimContacts(m).count(), 2);
    QCOMPARE(m_controller->busy(), false);

    m_controller->simPresenceChanged(true);

    // Report the SIM card removed
    m_controller->simPresenceChanged(false);
    QCOMPARE(m_controller->busy(), true);
    QTRY_VERIFY(m_controller->busy() == false);

    // All sim contacts should be removed
    QList<QContact> simContacts(getAllSimContacts(m));
    QCOMPARE(simContacts.count(), 0);
}
/*! Parses SIM contacts in TLV format.
 *
 * \param rawData SIM contacts in TLV format.
 * \return List of contacts.
*/
QList<QContact> CntSimStorePrivate::decodeSimContactsL(TDes8& rawData) const
{
    PbkPrintToLog(_L("CntSymbianSimEngine::decodeSimContactsL() - IN"));
    QList<QContact> fetchedContacts;
    QContact currentContact;

    TBuf16<KDataClientBuf> buffer;
    TPtrC16 bufPtr(buffer);

    TUint8 tagValue(0);
    CPhoneBookBuffer::TPhBkTagType dataType;

    bool isAdditionalNumber = false;

    CPhoneBookBuffer* pbBuffer = new(ELeave) CPhoneBookBuffer();
    CleanupStack::PushL(pbBuffer);
    pbBuffer->Set(&rawData);
    pbBuffer->StartRead();

    while (pbBuffer->GetTagAndType(tagValue, dataType) == KErrNone) {
        switch (tagValue)
        {
            case RMobilePhoneBookStore::ETagPBAdnIndex:
            {
                //save contact's id (SIM card index) and manager's name
                TUint16  index;
                if (pbBuffer->GetValue(index) == KErrNone) {
                    QScopedPointer<QContactId> contactId(new QContactId());
                    contactId->setLocalId(index);
                    contactId->setManagerUri(m_managerUri);
                    currentContact.setId(*contactId);
                }
                isAdditionalNumber = false;
                break;
            }
            case RMobilePhoneBookStore::ETagPBTonNpi:
            {
                // Note, that TON info can be incorporated into the phone number by Etel
                // implementation (TSY). E.g. this is the case with Nokia TSY.
                // Here general case is implemented.

                // Check number type, we are only interested if it's international or not.
                // We assume here that ETagPBTonNpi always comes after ETagPBNumber, not before.
                TUint8  tonNpi;
                if (pbBuffer->GetValue(tonNpi) == KErrNone) {
                    TUint8  intFlag = (tonNpi & KEtsiTonPosition) >> 4;
                    if (intFlag == 1) {
                        //international number format, append "+" to the last
                        //saved number
                        QList<QContactDetail> phoneNumbers = currentContact.details(
                                QContactPhoneNumber::DefinitionName);
                        if (phoneNumbers.count() > 0) {
                            QContactPhoneNumber lastNumber = static_cast<QContactPhoneNumber>(
                                phoneNumbers.at(phoneNumbers.count() - 1));
                            QString number = lastNumber.number();
                            number.insert(0, "+");
                            lastNumber.setNumber(number);
                            if (m_storeInfo.m_readOnlyAccess)
                                m_engine.setReadOnlyAccessConstraint(&lastNumber);
                            currentContact.saveDetail(&lastNumber);
                        }
                    }
                }

                // We have rearched to the end of the number,
                // invalidate additional number flag.
                isAdditionalNumber = false;
                break;
            }
            case RMobilePhoneBookStore::ETagPBText:
            {
                if (pbBuffer->GetValue(bufPtr) == KErrNone) {
                    if (isAdditionalNumber) {
                        // For additional number bufPtr contains number alpha string,
                        // this is ignored currently
                    }
                    else {
                        // Contact name otherwise
                        QContactName name;
                        QString nameString = QString::fromUtf16(bufPtr.Ptr(), bufPtr.Length());
                        name.setCustomLabel(nameString);
                        if (m_storeInfo.m_readOnlyAccess)
                            m_engine.setReadOnlyAccessConstraint(&name);
                        currentContact.saveDetail(&name);
                        QContactManager::Error error(QContactManager::NoError);
                        m_engine.setContactDisplayLabel(&currentContact, m_engine.synthesizedDisplayLabel(currentContact, &error));
                    }
                }
                break;
            }
            case RMobilePhoneBookStore::ETagPBSecondName:
            {
                if (pbBuffer->GetValue(bufPtr) == KErrNone) {
                    QContactNickname nickName;
                    QString name = QString::fromUtf16(bufPtr.Ptr(), bufPtr.Length());
                    nickName.setNickname(name);
                    if (m_storeInfo.m_readOnlyAccess)
                        m_engine.setReadOnlyAccessConstraint(&nickName);
                    currentContact.saveDetail(&nickName);
                }
                break;
            }
            case RMobilePhoneBookStore::ETagPBNumber:
            {
                if (pbBuffer->GetValue(bufPtr) == KErrNone) {
                    QContactPhoneNumber phoneNumber;
                    QString number = QString::fromUtf16(bufPtr.Ptr(), bufPtr.Length());
                    phoneNumber.setNumber(number);
                    if (m_storeInfo.m_readOnlyAccess)
                        m_engine.setReadOnlyAccessConstraint(&phoneNumber);
                    currentContact.saveDetail(&phoneNumber);
                }
                break;
            }
            case RMobilePhoneBookStore::ETagPBAnrStart:
            {
                // This tag should precede every additional number entry
                isAdditionalNumber = true;
                break;
            }
            case RMobilePhoneBookStore::ETagPBEmailAddress:
            {
                if (pbBuffer->GetValue(bufPtr) == KErrNone) {
                    QContactEmailAddress email;
                    QString emailAddress = QString::fromUtf16(bufPtr.Ptr(), bufPtr.Length());
                    email.setEmailAddress(emailAddress);
                    if (m_storeInfo.m_readOnlyAccess)
                        m_engine.setReadOnlyAccessConstraint(&email);
                    currentContact.saveDetail(&email);
                }
                break;
            }
            case RMobilePhoneBookStore::ETagPBNewEntry:
            {
                // This signals the end of the entry and is a special case
                // which will be handled below.
                break;
            }
            default:
            {
                // An unsupported field type - just skip this value
                pbBuffer->SkipValue(dataType);
                break;
            }
        } //switch

        // save contact to the array of contact to be returned if the whole entry was extracted
        if ((tagValue == RMobilePhoneBookStore::ETagPBNewEntry && currentContact.localId() > 0) ||
            (pbBuffer->RemainingReadLength() == 0 && currentContact.localId() > 0)) {
            fetchedContacts.append(currentContact);
            //clear current contact
            currentContact.clearDetails();
            QScopedPointer<QContactId> contactId(new QContactId());
            contactId->setLocalId(0);
            contactId->setManagerUri(QString());
            currentContact.setId(*contactId);
        }
    } //while
Example #8
0
int main(int argc, char  *argv[])
{
    QCoreApplication application(argc, argv);

    QMap<QString, QString> parameters;
    parameters.insert(QString::fromLatin1("mergePresenceChanges"), QString::fromLatin1("false"));

    QContactManager manager(QString::fromLatin1("org.nemomobile.contacts.sqlite"), parameters);

    QContactFetchRequest request;
    request.setManager(&manager);

    // Perform an initial request to ensure the database has been created before we start timing
    request.start();
    request.waitForFinished();

    qint64 elapsedTimeTotal = 0;

    QElapsedTimer asyncTotalTimer;
    asyncTotalTimer.start();

    // Fetch all, no optimization hints
    for (int i = 0; i < 3; ++i) {
        QElapsedTimer timer;
        timer.start();
        request.start();
        request.waitForFinished();

        qint64 elapsed = timer.elapsed();
        qDebug() << i << ": Fetch completed in" << elapsed << "ms";
        elapsedTimeTotal += elapsed;
    }

    // Skip relationships
    QContactFetchHint hint;
    hint.setOptimizationHints(QContactFetchHint::NoRelationships);
    request.setFetchHint(hint);

    for (int i = 0; i < 3; ++i) {
        QElapsedTimer timer;
        timer.start();
        request.start();
        request.waitForFinished();

        qint64 elapsed = timer.elapsed();
        qDebug() << i << ": No-relationships fetch completed in" << elapsed << "ms";
        elapsedTimeTotal += elapsed;
    }

    // Reduce data access
#ifdef USING_QTPIM
    hint.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactName::Type << QContactAddress::Type);
#else
    hint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName << QContactAddress::DefinitionName);
#endif
    request.setFetchHint(hint);

    for (int i = 0; i < 3; ++i) {
        QElapsedTimer timer;
        timer.start();
        request.start();
        request.waitForFinished();

        qint64 elapsed = timer.elapsed();
        qDebug() << i << ": Reduced data fetch completed in" << elapsed << "ms";
        elapsedTimeTotal += elapsed;
    }

    // Reduce number of results
    hint.setMaxCountHint(request.contacts().count() / 8);
    request.setFetchHint(hint);

    for (int i = 0; i < 3; ++i) {
        QElapsedTimer timer;
        timer.start();
        request.start();
        request.waitForFinished();

        qint64 elapsed = timer.elapsed();
        qDebug() << i << ": Max count fetch completed in" << elapsed << "ms";
        elapsedTimeTotal += elapsed;
    }
    qint64 asyncTotalElapsed = asyncTotalTimer.elapsed();



    // Time some synchronous operations.  First, generate the test data.
    qsrand((int)asyncTotalElapsed);
    QList<int> nbrContacts;
    nbrContacts << 10 << 100 << 500 << 1000 << 2000;
    QList<QList<QContact> > testData;
    qDebug() << "\n\n\n\n\n";
    qDebug() << "Generating test data for timings...";
    for (int i = 0; i < nbrContacts.size(); ++i) {
        int howMany = nbrContacts.at(i);
        QList<QContact> newTestData;
        newTestData.reserve(howMany);

        for (int j = 0; j < howMany; ++j) {
            // Use testing sync target, so 'local' won't be modified into 'was_local' via aggregation
            newTestData.append(generateContact(QString::fromLatin1("testing")));
        }

        testData.append(newTestData);
    }


    // Perform the timings - these all create new contacts and assume an "empty" initial database
    QElapsedTimer syncTimer;
    for (int i = 0; i < testData.size(); ++i) {
        QList<QContact> td = testData.at(i);
        qint64 ste = 0;
        qDebug() << "Performing tests for" << td.size() << "contacts:";

        syncTimer.start();
        manager.saveContacts(&td);
        ste = syncTimer.elapsed();
        qDebug() << "    saving took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        QContactDetailFilter testingFilter;
#ifdef USING_QTPIM
        testingFilter.setDetailType(QContactSyncTarget::Type, QContactSyncTarget::FieldSyncTarget);
#else
        testingFilter.setDetailDefinitionName(QContactSyncTarget::DefinitionName, QContactSyncTarget::FieldSyncTarget);
#endif
        testingFilter.setValue(QString::fromLatin1("testing"));

        QContactFetchHint fh;
        syncTimer.start();
        QList<QContact> readContacts = manager.contacts(testingFilter, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        if (readContacts.size() != td.size()) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << td.size();
        }
        qDebug() << "    reading all (" << readContacts.size() << "), all details, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDisplayLabel::Type
                << QContactName::Type << QContactAvatar::Type
                << QContactPhoneNumber::Type << QContactEmailAddress::Type);
#else
        fh.setDetailDefinitionsHint(QStringList() << QContactDisplayLabel::DefinitionName
                << QContactName::DefinitionName << QContactAvatar::DefinitionName
                << QContactPhoneNumber::DefinitionName << QContactEmailAddress::DefinitionName);
#endif
        syncTimer.start();
        readContacts = manager.contacts(testingFilter, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        if (readContacts.size() != td.size()) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << td.size();
        }
        qDebug() << "    reading all, common details, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        fh.setOptimizationHints(QContactFetchHint::NoRelationships);
#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>());
#else
        fh.setDetailDefinitionsHint(QStringList());
#endif
        syncTimer.start();
        readContacts = manager.contacts(testingFilter, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        if (readContacts.size() != td.size()) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << td.size();
        }
        qDebug() << "    reading all, no relationships, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDisplayLabel::Type
                << QContactName::Type << QContactAvatar::Type);
#else
        fh.setDetailDefinitionsHint(QStringList() << QContactDisplayLabel::DefinitionName
                << QContactName::DefinitionName << QContactAvatar::DefinitionName);
#endif
        syncTimer.start();
        readContacts = manager.contacts(testingFilter, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        if (readContacts.size() != td.size()) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << td.size();
        }
        qDebug() << "    reading all, display details + no rels, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        // Read the contacts, selected by ID
        QList<QContactId> idsToRetrieve;
        for (int j = 0; j < td.size(); ++j) {
            idsToRetrieve.append(retrievalId(td.at(j)));
        }

        syncTimer.start();
        readContacts = manager.contacts(idsToRetrieve, fh, 0);
        ste = syncTimer.elapsed();
        if (readContacts.size() != td.size()) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << td.size();
        }
        qDebug() << "    reading all by IDs, display details + no rels, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        // Read the same set using ID filtering
#ifdef USING_QTPIM
        QContactIdFilter idFilter;
#else
        QContactLocalIdFilter idFilter;
#endif
        idFilter.setIds(idsToRetrieve);

        syncTimer.start();
        readContacts = manager.contacts(idFilter, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        if (readContacts.size() != td.size()) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << td.size();
        }
        qDebug() << "    reading all by ID filter, display details + no rels, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        // Read the same set, but filter everything out using syncTarget
        QContactDetailFilter aggregateFilter;
#ifdef USING_QTPIM
        aggregateFilter.setDetailType(QContactSyncTarget::Type, QContactSyncTarget::FieldSyncTarget);
#else
        aggregateFilter.setDetailDefinitionName(QContactSyncTarget::DefinitionName, QContactSyncTarget::FieldSyncTarget);
#endif
        aggregateFilter.setValue(QString::fromLatin1("aggregate"));

        syncTimer.start();
        readContacts = manager.contacts(idFilter & aggregateFilter, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        if (readContacts.size() != 0) {
            qWarning() << "Invalid retrieval count:" << readContacts.size() << "expecting:" << 0;
        }
        qDebug() << "    reading all by ID filter & aggregate, display details + no rels, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        QContactDetailFilter firstNameStartsA;
#ifdef USING_QTPIM
        firstNameStartsA.setDetailType(QContactName::Type, QContactName::FieldFirstName);
#else
        firstNameStartsA.setDetailDefinitionName(QContactName::DefinitionName, QContactName::FieldFirstName);
#endif
        firstNameStartsA.setValue("A");
        firstNameStartsA.setMatchFlags(QContactDetailFilter::MatchStartsWith);
#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>());
#else
        fh.setDetailDefinitionsHint(QStringList());
#endif
        syncTimer.start();
        readContacts = manager.contacts(firstNameStartsA, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        qDebug() << "    reading filtered (" << readContacts.size() << "), no relationships, took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        QList<ContactIdType> idsToRemove;
        for (int j = 0; j < td.size(); ++j) {
            idsToRemove.append(retrievalId(td.at(j)));
        }

        syncTimer.start();
        manager.removeContacts(idsToRemove);
        ste = syncTimer.elapsed();
        qDebug() << "    removing test data took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;
    }

    // these tests are slightly different to those above.  They operate on much smaller
    // batches, but occur after the database has already been prefilled with some data.
    QList<int> smallerNbrContacts;
    smallerNbrContacts << 1 << 2 << 5 << 10 << 20 << 50;
    QList<QList<QContact> > smallerTestData;
    qDebug() << "\n\nGenerating smaller test data for prefilled timings...";
    for (int i = 0; i < smallerNbrContacts.size(); ++i) {
        int howMany = smallerNbrContacts.at(i);
        QList<QContact> newTestData;
        newTestData.reserve(howMany);

        for (int j = 0; j < howMany; ++j) {
            newTestData.append(generateContact());
        }

        smallerTestData.append(newTestData);
    }

    // prefill the database
    QList<QContact> prefillData;
    for (int i = 0; i < testData.size() && testData.at(i).size() < 1001; ++i) {
        prefillData = testData.at(i);
    }
    qDebug() << "Prefilling database with" << prefillData.size() << "contacts... this will take a while...";
    manager.saveContacts(&prefillData);
    qDebug() << "Now performing timings (shouldn't get aggregated)...";
    for (int i = 0; i < smallerTestData.size(); ++i) {
        QList<QContact> td = smallerTestData.at(i);
        qint64 ste = 0;
        qDebug() << "Performing tests for" << td.size() << "contacts:";

        syncTimer.start();
        manager.saveContacts(&td);
        ste = syncTimer.elapsed();
        qDebug() << "    saving took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;

        QContactFetchHint fh;
        syncTimer.start();
        QList<QContact> readContacts = manager.contacts(QContactFilter(), QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        qDebug() << "    reading all (" << readContacts.size() << "), all details, took" << ste << "milliseconds";
        elapsedTimeTotal += ste;

#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDisplayLabel::Type
                << QContactName::Type << QContactAvatar::Type
                << QContactPhoneNumber::Type << QContactEmailAddress::Type);
#else
        fh.setDetailDefinitionsHint(QStringList() << QContactDisplayLabel::DefinitionName
                << QContactName::DefinitionName << QContactAvatar::DefinitionName
                << QContactPhoneNumber::DefinitionName << QContactEmailAddress::DefinitionName);
#endif
        syncTimer.start();
        readContacts = manager.contacts(QContactFilter(), QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        qDebug() << "    reading all, common details, took" << ste << "milliseconds";
        elapsedTimeTotal += ste;

        fh.setOptimizationHints(QContactFetchHint::NoRelationships);
#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>());
#else
        fh.setDetailDefinitionsHint(QStringList());
#endif
        syncTimer.start();
        readContacts = manager.contacts(QContactFilter(), QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        qDebug() << "    reading all, no relationships, took" << ste << "milliseconds";
        elapsedTimeTotal += ste;

#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDisplayLabel::Type
                << QContactName::Type << QContactAvatar::Type);
#else
        fh.setDetailDefinitionsHint(QStringList() << QContactDisplayLabel::DefinitionName
                << QContactName::DefinitionName << QContactAvatar::DefinitionName);
#endif
        syncTimer.start();
        readContacts = manager.contacts(QContactFilter(), QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        qDebug() << "    reading all, display details + no rels, took" << ste << "milliseconds";
        elapsedTimeTotal += ste;

        QContactDetailFilter firstNameStartsA;
#ifdef USING_QTPIM
        firstNameStartsA.setDetailType(QContactName::Type, QContactName::FieldFirstName);
#else
        firstNameStartsA.setDetailDefinitionName(QContactName::DefinitionName, QContactName::FieldFirstName);
#endif
        firstNameStartsA.setValue("A");
        firstNameStartsA.setMatchFlags(QContactDetailFilter::MatchStartsWith);
#ifdef USING_QTPIM
        fh.setDetailTypesHint(QList<QContactDetail::DetailType>());
#else
        fh.setDetailDefinitionsHint(QStringList());
#endif
        syncTimer.start();
        readContacts = manager.contacts(firstNameStartsA, QList<QContactSortOrder>(), fh);
        ste = syncTimer.elapsed();
        qDebug() << "    reading filtered (" << readContacts.size() << "), no relationships, took" << ste << "milliseconds";
        elapsedTimeTotal += ste;

        QList<ContactIdType> idsToRemove;
        for (int j = 0; j < td.size(); ++j) {
            idsToRemove.append(retrievalId(td.at(j)));
        }

        syncTimer.start();
        manager.removeContacts(idsToRemove);
        ste = syncTimer.elapsed();
        qDebug() << "    removing test data took" << ste << "milliseconds (" << ((1.0 * ste) / (1.0 * td.size())) << "msec per contact )";
        elapsedTimeTotal += ste;
    }

    // The next test is about saving contacts which should get aggregated into others.
    // Aggregation is an expensive operation, so we expect these save operations to take longer.
    qDebug() << "\n\nPerforming aggregation tests";
    QList<QContact> contactsToAggregate;
    for (int i = 0; i < 100; ++i) {
        QContact existingContact = prefillData.at(prefillData.size() - 1 - i);
        QContact contactToAggregate;
        QContactSyncTarget newSyncTarget;
        newSyncTarget.setSyncTarget(QString(QLatin1String("fetchtimes-aggregation")));
        QContactName aggName = existingContact.detail<QContactName>(); // ensures it'll get aggregated
        QContactOnlineAccount newOnlineAcct; // new data, which should get promoted up etc.
        newOnlineAcct.setAccountUri(QString(QLatin1String("test-aggregation-%1@fetchtimes")).arg(i));
        contactToAggregate.saveDetail(&newSyncTarget);
        contactToAggregate.saveDetail(&aggName);
        contactToAggregate.saveDetail(&newOnlineAcct);
        contactsToAggregate.append(contactToAggregate);
    }

    syncTimer.start();
    manager.saveContacts(&contactsToAggregate);
    qint64 aggregationElapsed = syncTimer.elapsed();
    int totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "Average time for aggregation of" << contactsToAggregate.size() << "contacts (with" << totalAggregatesInDatabase << "existing in database):" << aggregationElapsed
             << "milliseconds (" << ((1.0 * aggregationElapsed) / (1.0 * contactsToAggregate.size())) << " msec per aggregated contact )";
    elapsedTimeTotal += aggregationElapsed;

    // Now perform the test again, this time with more aggregates, to test nonlinearity.
    contactsToAggregate.clear();
    for (int i = 200; i < 400; ++i) {
        QContact existingContact = prefillData.at(prefillData.size() - 1 - i);
        QContact contactToAggregate;
        QContactSyncTarget newSyncTarget;
        newSyncTarget.setSyncTarget(QString(QLatin1String("fetchtimes-aggregation")));
        QContactName aggName = existingContact.detail<QContactName>(); // ensures it'll get aggregated
        QContactOnlineAccount newOnlineAcct; // new data, which should get promoted up etc.
        newOnlineAcct.setAccountUri(QString(QLatin1String("test-aggregation-%1@fetchtimes")).arg(i));
        contactToAggregate.saveDetail(&newSyncTarget);
        contactToAggregate.saveDetail(&aggName);
        contactToAggregate.saveDetail(&newOnlineAcct);
        contactsToAggregate.append(contactToAggregate);
    }

    syncTimer.start();
    manager.saveContacts(&contactsToAggregate);
    aggregationElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "Average time for aggregation of" << contactsToAggregate.size() << "contacts (with" << totalAggregatesInDatabase << "existing in database):" << aggregationElapsed
             << "milliseconds (" << ((1.0 * aggregationElapsed) / (1.0 * contactsToAggregate.size())) << " msec per aggregated contact )";
    elapsedTimeTotal += aggregationElapsed;


    // The next test is about updating existing contacts, amongst a large set.
    // We're especially interested in presence updates, as these are common.
    qDebug() << "\n\nPerforming presence update tests:";

    // in the first presence update test, we update a small number of contacts.
    QStringList presenceAvatars = generateAvatarsList();
    QList<QContact> contactsToUpdate;
    for (int i = 0; i < 10; ++i) {
        contactsToUpdate.append(prefillData.at(prefillData.size() - 1 - i));
    }

    // modify the presence, nickname and avatar of the test data
    for (int j = 0; j < contactsToUpdate.size(); ++j) {
        QString genstr = QString::number(j);
        QContact curr = contactsToUpdate[j];
        QContactPresence cp = curr.detail<QContactPresence>();
        QContactNickname nn = curr.detail<QContactNickname>();
        QContactAvatar av = curr.detail<QContactAvatar>();
        cp.setNickname(genstr);
        cp.setCustomMessage(genstr);
        cp.setTimestamp(QDateTime::currentDateTime());
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>(qrand() % 4));
        nn.setNickname(nn.nickname() + genstr);
        av.setImageUrl(genstr + presenceAvatars.at(qrand() % presenceAvatars.size()));
        curr.saveDetail(&cp);
        curr.saveDetail(&nn);
        curr.saveDetail(&av);
        contactsToUpdate.replace(j, curr);
    }

    // perform a batch save.
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate);
    qint64 presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") presence+nick+avatar (with" << totalAggregatesInDatabase << "existing in database, all overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // in the second presence update test, we update ALL of the contacts
    // This simulates having a large number of contacts from a single source (eg, a social network)
    // where (due to changed connectivity status) presence updates for the entire set become available.
    contactsToUpdate.clear();
    QDateTime timestamp = QDateTime::currentDateTime();
    for (int j = 0; j < prefillData.size(); ++j) {
        QContact curr = prefillData.at(j);
        QString genstr = QString::number(j) + "2";
        QContactPresence cp = curr.detail<QContactPresence>();
        QContactNickname nn = curr.detail<QContactNickname>();
        QContactAvatar av = curr.detail<QContactAvatar>();
        cp.setNickname(genstr);
        cp.setCustomMessage(genstr);
        cp.setTimestamp(timestamp);
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        nn.setNickname(nn.nickname() + genstr);
        av.setImageUrl(genstr + presenceAvatars.at(qrand() % presenceAvatars.size()));
        curr.saveDetail(&cp);
        curr.saveDetail(&nn);
        curr.saveDetail(&av);
        contactsToUpdate.append(curr);
    }

    // perform a batch save.
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate);
    presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") presence+nick+avatar (with" << totalAggregatesInDatabase << "existing in database, all overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // the third presence update test is identical to the previous, but with 2000 prefilled contacts in database.
    qDebug() << "    Adding more prefill data, please wait...";
    QList<QContact> morePrefillData;
    for (int i = 0; i < 1000; ++i) {
        morePrefillData.append(generateContact(QString::fromLatin1("testing")));
    }
    manager.saveContacts(&morePrefillData);

    // now do the updates and save.
    contactsToUpdate.clear();
    timestamp = QDateTime::currentDateTime();
    for (int j = 0; j < prefillData.size(); ++j) {
        QContact curr = prefillData.at(j);
        QString genstr = QString::number(j) + "3";
        QContactPresence cp = curr.detail<QContactPresence>();
        QContactNickname nn = curr.detail<QContactNickname>();
        QContactAvatar av = curr.detail<QContactAvatar>();
        cp.setNickname(genstr);
        cp.setCustomMessage(genstr);
        cp.setTimestamp(timestamp);
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        nn.setNickname(nn.nickname() + genstr);
        av.setImageUrl(genstr + presenceAvatars.at(qrand() % presenceAvatars.size()));
        curr.saveDetail(&cp);
        curr.saveDetail(&nn);
        curr.saveDetail(&av);
        contactsToUpdate.append(curr);
    }
    for (int j = 0; j < morePrefillData.size(); ++j) {
        QContact curr = morePrefillData.at(j);
        QString genstr = QString::number(j) + "3";
        QContactPresence cp = curr.detail<QContactPresence>();
        QContactNickname nn = curr.detail<QContactNickname>();
        QContactAvatar av = curr.detail<QContactAvatar>();
        cp.setNickname(genstr);
        cp.setCustomMessage(genstr);
        cp.setTimestamp(timestamp);
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        nn.setNickname(nn.nickname() + genstr);
        av.setImageUrl(genstr + presenceAvatars.at(qrand() % presenceAvatars.size()));
        curr.saveDetail(&cp);
        curr.saveDetail(&nn);
        curr.saveDetail(&av);
        contactsToUpdate.append(curr);
    }

    // perform a batch save.
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate);
    presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") presence+nick+avatar (with" << totalAggregatesInDatabase << "existing in database, all overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // clean up the "more prefill data"
    qDebug() << "    cleaning up extra prefill data, please wait...";
    QList<ContactIdType> morePrefillIds;
    for (int j = 0; j < morePrefillData.size(); ++j) {
        morePrefillIds.append(retrievalId(morePrefillData.at(j)));
    }

    manager.removeContacts(morePrefillIds);

    // the fourth presence update test checks update time for non-overlapping sets of data.
    qDebug() << "    generating non-overlapping / aggregated prefill data, please wait...";
    morePrefillData.clear();
    for (int i = 0; i < 1000; ++i) {
        morePrefillData.append(generateContact("test-presence-4", false)); // false = don't aggregate.
    }
    manager.saveContacts(&morePrefillData);

    // now do the update
    contactsToUpdate.clear();
    timestamp = QDateTime::currentDateTime();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        QContact curr = morePrefillData.at(j);
        QString genstr = QString::number(j) + "4";
        QContactPresence cp = curr.detail<QContactPresence>();
        QContactNickname nn = curr.detail<QContactNickname>();
        QContactAvatar av = curr.detail<QContactAvatar>();
        cp.setNickname(genstr);
        cp.setCustomMessage(genstr);
        cp.setTimestamp(timestamp);
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        nn.setNickname(nn.nickname() + genstr);
        av.setImageUrl(genstr + presenceAvatars.at(qrand() % presenceAvatars.size()));
        curr.saveDetail(&cp);
        curr.saveDetail(&nn);
        curr.saveDetail(&av);
        contactsToUpdate.append(curr);
    }

    // perform a batch save.
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate);
    presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") presence+nick+avatar (with" << totalAggregatesInDatabase << "existing in database, no overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // clean up the "more prefill data"
    qDebug() << "    cleaning up extra prefill data, please wait...";
#ifdef USING_QTPIM
    morePrefillIds.clear();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        morePrefillIds.append(morePrefillData.at(j).id());
    }
#else
    morePrefillIds.clear();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        morePrefillIds.append(morePrefillData.at(j).localId());
    }
#endif
    manager.removeContacts(morePrefillIds);

    // the fifth presence update test is similar to the above except that half of
    // the extra contacts have a (high) chance of being aggregated into an existing contact.
    // So, database should have 2000 constituents, 1000 from "local", 1000 from "test-presence-5"
    // with 1500 aggregates (about 500 of test-presence-5 contacts will share an aggregate with
    // a local contact).  TODO: check what happens if multiple aggregates for local contacts
    // could possibly match a given test-presence-5 contact (which is possible, since the backend
    // never aggregates two contacts from the same sync source...)
    qDebug() << "    generating partially-overlapping / aggregated prefill data, please wait...";
    morePrefillData.clear();
    for (int i = 0; i < 1000; ++i) {
        if (i < 500) {
            morePrefillData.append(generateContact("test-presence-5", false)); // false = don't aggregate.
        } else {
            morePrefillData.append(generateContact("test-presence-5", true));  // true = possibly aggregate.
        }
    }
    manager.saveContacts(&morePrefillData);

    // now do the update
    contactsToUpdate.clear();
    timestamp = QDateTime::currentDateTime();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        QContact curr = morePrefillData.at(j);
        QString genstr = QString::number(j) + "5";
        QContactPresence cp = curr.detail<QContactPresence>();
        QContactNickname nn = curr.detail<QContactNickname>();
        QContactAvatar av = curr.detail<QContactAvatar>();
        cp.setNickname(genstr);
        cp.setCustomMessage(genstr);
        cp.setTimestamp(timestamp);
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        nn.setNickname(nn.nickname() + genstr);
        av.setImageUrl(genstr + presenceAvatars.at(qrand() % presenceAvatars.size()));
        curr.saveDetail(&cp);
        curr.saveDetail(&nn);
        curr.saveDetail(&av);
        contactsToUpdate.append(curr);
    }

    // perform a batch save.
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate);
    presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") presence+nick+avatar (with" << totalAggregatesInDatabase << "existing in database, 500 overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // the sixth presence update test is identical to the fifth test, except that we ONLY
    // update the presence status (not nickname or avatar).
    morePrefillData = contactsToUpdate;
    contactsToUpdate.clear();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        QContact curr = morePrefillData.at(j);
        QContactPresence cp = curr.detail<QContactPresence>();
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        curr.saveDetail(&cp);
        contactsToUpdate.append(curr);
    }

    // perform a batch save.
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate);
    presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") presence only (with" << totalAggregatesInDatabase << "existing in database, 500 overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // the seventh presence update test is identical to the 6th test, except that
    // we also pass a "detail type mask" to the update.  This allows the backend
    // to perform optimisation based upon which details are modified.
    morePrefillData = contactsToUpdate;
    contactsToUpdate.clear();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        QContact curr = morePrefillData.at(j);
        QContactPresence cp = curr.detail<QContactPresence>();
        cp.setPresenceState(static_cast<QContactPresence::PresenceState>((qrand() % 4) + 1));
        curr.saveDetail(&cp);
        contactsToUpdate.append(curr);
    }

    // perform a batch save.
#ifdef USING_QTPIM
    QList<QContactDetail::DetailType> typeMask;
    typeMask << QContactDetail::TypePresence;
#else
    QStringList typeMask;
    typeMask << QString(QLatin1String(QContactPresence::DefinitionName));
#endif
    syncTimer.start();
    manager.saveContacts(&contactsToUpdate, typeMask);
    presenceElapsed = syncTimer.elapsed();
    totalAggregatesInDatabase = manager.contactIds().count();
    qDebug() << "    update ( batch of" << contactsToUpdate.size() << ") masked presence only (with" << totalAggregatesInDatabase << "existing in database, 500 overlap):" << presenceElapsed
             << "milliseconds (" << ((1.0 * presenceElapsed) / (1.0 * contactsToUpdate.size())) << " msec per updated contact )";
    elapsedTimeTotal += presenceElapsed;

    // clean up the "more prefill data"
    qDebug() << "    cleaning up extra prefill data, please wait...";
#ifdef USING_QTPIM
    morePrefillIds.clear();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        morePrefillIds.append(morePrefillData.at(j).id());
    }
#else
    morePrefillIds.clear();
    for (int j = 0; j < morePrefillData.size(); ++j) {
        morePrefillIds.append(morePrefillData.at(j).localId());
    }
#endif
    manager.removeContacts(morePrefillIds);

    qDebug() << "\n\nCumulative elapsed time:" << elapsedTimeTotal << "milliseconds";
    return 0;
}
QString SeasidePerson::nickname() const
{
    QContactNickname nameDetail = mContact.detail<QContactNickname>();
    return nameDetail.nickname();
}
void AsyncRequestExample::performRequests()
{
//! [Creating a new contact in a manager]
    QContact exampleContact;

    QContactName nameDetail;
    nameDetail.setFirstName("Adam");
    nameDetail.setLastName("Unlikely");

    QContactPhoneNumber phoneNumberDetail;
    phoneNumberDetail.setNumber("+123 4567");

    exampleContact.saveDetail(&nameDetail);
    exampleContact.saveDetail(&phoneNumberDetail);

    // save the newly created contact in the manager
    connect(&m_contactSaveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(contactSaveRequestStateChanged(QContactAbstractRequest::State)));
    m_contactSaveRequest.setManager(m_manager);
    m_contactSaveRequest.setContacts(QList<QContact>() << exampleContact);
    m_contactSaveRequest.start();
//! [Creating a new contact in a manager]

    m_contactSaveRequest.waitForFinished();

//! [Creating a new contact in a manager waiting until finished]
    m_contactSaveRequest.setManager(m_manager);
    m_contactSaveRequest.setContacts(QList<QContact>() << exampleContact);
    m_contactSaveRequest.start();
    m_contactSaveRequest.waitForFinished();
    QList<QContact> savedContacts = m_contactSaveRequest.contacts();
//! [Creating a new contact in a manager waiting until finished]

//! [Filtering contacts from a manager]
    connect(&m_contactFetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(contactFetchRequestStateChanged(QContactAbstractRequest::State)));
    m_contactFetchRequest.setManager(m_manager);
    m_contactFetchRequest.setFilter(QContactPhoneNumber::match("+123 4567"));
    m_contactFetchRequest.start();
//! [Filtering contacts from a manager]

    m_contactFetchRequest.waitForFinished();

//! [Retrieving an existing contact from a manager]
    QContactLocalIdFilter idListFilter;
    idListFilter.setIds(QList<QContactLocalId>() << exampleContact.localId());
    m_contactFetchRequest.setManager(m_manager);
    m_contactFetchRequest.setFilter(idListFilter);
    m_contactFetchRequest.start();
//! [Retrieving an existing contact from a manager]

    m_contactFetchRequest.waitForFinished();

//! [Updating an existing contact in a manager]
    phoneNumberDetail.setNumber("+123 9876");
    exampleContact.saveDetail(&phoneNumberDetail);
    m_contactSaveRequest.setManager(m_manager);
    m_contactSaveRequest.setContacts(QList<QContact>() << exampleContact);
    m_contactSaveRequest.start();
//! [Updating an existing contact in a manager]

    m_contactFetchRequest.waitForFinished();

//! [Removing a contact from a manager]
    connect(&m_contactRemoveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(contactRemoveRequestStateChanged(QContactAbstractRequest::State)));
    m_contactRemoveRequest.setManager(m_manager);
    m_contactRemoveRequest.setContactIds(QList<QContactLocalId>() << exampleContact.localId());
    m_contactRemoveRequest.start();
//! [Removing a contact from a manager]

    m_contactFetchRequest.waitForFinished();

//! [Creating a new relationship between two contacts]
    // first, create the group and the group member
    QContact exampleGroup;
    exampleGroup.setType(QContactType::TypeGroup);
    QContactNickname groupName;
    groupName.setNickname("Example Group");
    exampleGroup.saveDetail(&groupName);
    
    QContact exampleGroupMember;
    QContactName groupMemberName;
    groupMemberName.setFirstName("Member");
    exampleGroupMember.saveDetail(&groupMemberName);

    // second, save those contacts in the manager
    QList<QContact> saveList;
    saveList << exampleGroup << exampleGroupMember;
    m_contactSaveRequest.setContacts(saveList);
    m_contactSaveRequest.start();
    m_contactSaveRequest.waitForFinished();

    // third, create the relationship between those contacts
    QContactRelationship groupRelationship;
    groupRelationship.setFirst(exampleGroup.id());
    groupRelationship.setRelationshipType(QContactRelationship::HasMember);
    groupRelationship.setSecond(exampleGroupMember.id());

    // finally, save the relationship in the manager
    connect(&m_relationshipSaveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(relationshipSaveRequestStateChanged(QContactAbstractRequest::State)));
    m_relationshipSaveRequest.setManager(m_manager);
    m_relationshipSaveRequest.setRelationships(QList<QContactRelationship>() << groupRelationship);
    m_relationshipSaveRequest.start();
//! [Creating a new relationship between two contacts]

    m_contactFetchRequest.waitForFinished();

//! [Retrieving relationships between contacts]
    connect(&m_relationshipFetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(relationshipFetchRequestStateChanged(QContactAbstractRequest::State)));
    m_relationshipFetchRequest.setManager(m_manager);
    // retrieve the list of relationships between the example group contact and the example member contact
    // where the group contact is the first contact in the relationship, and the member contact is the
    // second contact in the relationship.  In order to fetch all relationships between them, another
    // relationship fetch must be performed with their roles reversed, and the results added together.
    m_relationshipFetchRequest.setFirst(exampleGroup.id());
    m_relationshipFetchRequest.setSecond(exampleGroupMember.id());
    m_relationshipFetchRequest.start();
//! [Retrieving relationships between contacts]

    m_contactFetchRequest.waitForFinished();

//! [Providing a fetch hint]
    QContactFetchHint hasMemberRelationshipsOnly;
    hasMemberRelationshipsOnly.setRelationshipTypesHint(QStringList(QContactRelationship::HasMember));

    m_contactFetchRequest.setManager(m_manager);
    m_contactFetchRequest.setFilter(QContactFilter()); // all contacts
    m_contactFetchRequest.setFetchHint(hasMemberRelationshipsOnly);
    m_contactFetchRequest.start();
//! [Providing a fetch hint]

//! [Removing a relationship]
    connect(&m_relationshipRemoveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(relationshipRemoveRequestStateChanged(QContactAbstractRequest::State)));
    m_relationshipRemoveRequest.setManager(m_manager);
    m_relationshipRemoveRequest.setRelationships(QList<QContactRelationship>() << groupRelationship);
    m_relationshipRemoveRequest.start();
//! [Removing a relationship]

    connect(&m_definitionFetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(definitionFetchRequestStateChanged(QContactAbstractRequest::State)));
//! [Querying the schema supported by a manager]
    m_definitionFetchRequest.setManager(m_manager);
    m_definitionFetchRequest.setDefinitionNames(QStringList(QContactName::DefinitionName));
    m_definitionFetchRequest.start();
    m_definitionFetchRequest.waitForFinished();
    QMap<QString, QContactDetailDefinition> definitions = m_definitionFetchRequest.definitions();
    qDebug() << "This manager"
             << (definitions.value(QContactName::DefinitionName).fields().contains(QContactName::FieldCustomLabel) ? "supports" : "does not support")
             << "the custom label field of QContactName";
//! [Querying the schema supported by a manager]

    connect(&m_definitionSaveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(definitionSaveRequestStateChanged(QContactAbstractRequest::State)));
    connect(&m_definitionRemoveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(definitionRemoveRequestStateChanged(QContactAbstractRequest::State)));
//! [Modifying the schema supported by a manager]
    // modify the name definition, adding a patronym field
    QContactDetailDefinition nameDefinition = definitions.value(QContactName::DefinitionName);
    QContactDetailFieldDefinition fieldPatronym;
    fieldPatronym.setDataType(QVariant::String);
    nameDefinition.insertField("Patronym", fieldPatronym);

    // save the updated definition in the manager if supported...
    if (m_manager->hasFeature(QContactManager::MutableDefinitions)) {
        m_definitionSaveRequest.setManager(m_manager);
        m_definitionSaveRequest.setContactType(QContactType::TypeContact);
        m_definitionSaveRequest.setDefinitions(QList<QContactDetailDefinition>() << nameDefinition);
        m_definitionSaveRequest.start();
    }
//! [Modifying the schema supported by a manager]

    QCoreApplication::exit(0);
}