qtmobility/plugins/contacts/symbian/src/filtering/cntsymbianfilterdbms.cpp
changeset 1 2b40d63a9c3d
child 4 90517678cc4f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qtmobility/plugins/contacts/symbian/src/filtering/cntsymbianfilterdbms.cpp	Fri Apr 16 15:51:22 2010 +0300
@@ -0,0 +1,517 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SYMBIAN_BACKEND_USE_SQLITE
+
+#include <qcontactdetail.h>
+#include <QSet>
+
+#include "cntsymbianfilterdbms.h"
+#include "cnttransformcontact.h"
+#include "cntsymbiantransformerror.h"
+
+#include <cntdb.h>
+#include <cntfield.h>
+#include <centralrepository.h>
+
+#include "qcontactname.h"
+#include "qcontactdetailfilter.h"
+#include "qcontactphonenumber.h"
+#include "cntsymbiansorterdbms.h"
+
+// Telephony Configuration API
+// Keys under this category are used in defining telephony configuration.
+const TUid KCRUidTelConfiguration = {0x102828B8};
+// Amount of digits to be used in contact matching.
+// This allows a customer to variate the amount of digits to be matched.
+const TUint32 KTelMatchDigits                               = 0x00000001;
+// Default match length
+const TInt KDefaultMatchLength(7);
+
+CntSymbianFilter::CntSymbianFilter(CContactDatabase& contactDatabase):
+    m_contactDatabase(contactDatabase),
+    m_contactSorter(0),
+    m_transformContact(0)
+{
+    // TODO: take CntTransformContact ref as a parameter?
+    m_transformContact = new CntTransformContact;
+    m_contactSorter = new CntSymbianSorterDbms(m_contactDatabase, *m_transformContact);
+}
+
+CntSymbianFilter::~CntSymbianFilter()
+{
+    delete m_contactSorter;
+    delete m_transformContact;
+}
+
+/*!
+ * The contact database version implementation for QContactManager::contacts
+ * function. See filterSupported for the list of supported filters.
+ * All the other filtering flags fallback to the generic filtering done
+ * in QContactManagerEngine (expected to be done by to the caller). Contacts
+ * are sorted only if the sort order is supported by contacts database. See
+ * CntSymbianSorterDbms::filterSupportLevel for the list of supported sort
+ * orders.
+ *
+ * \a filter The QContactFilter to be used.
+ * \a sortOrders The sort orders to be used. If the sort orders are not
+ * supported by contacts database this parameter is ignored and sorting needs
+ * to be done by the caller.
+ * \a error On return, contains the possible error in filtering/sorting.
+ */
+QList<QContactLocalId> CntSymbianFilter::contacts(
+    const QContactFilter &filter,
+    const QList<QContactSortOrder> &sortOrders,
+    bool &filterSupportedFlag,
+    QContactManager::Error &error)
+{
+    QList<QContactLocalId> result;
+
+    // No need to proceed if some of the filters in the chain is not supported
+    if(!filterSupportedFlag) return result;
+
+    // Intersection filter is handled by a recursive function call for each
+    // contained filter (unless at least one requires slow filtering)
+    if (filter.type() == QContactFilter::IntersectionFilter) {
+        QList<QContactFilter> filters = ((QContactIntersectionFilter) filter).filters();
+        for(int i(0); filterSupportedFlag && i < filters.count(); i++) {
+            if(result.isEmpty())
+                result = contacts(filters[i], sortOrders, filterSupportedFlag, error);
+            else
+                result = contacts(filters[i], sortOrders, filterSupportedFlag, error).toSet().intersect(result.toSet()).toList();
+        }
+    // Union filter is handled by a recursive function call for each
+    // contained filter (unless at least one requires slow filtering)
+    } else if (filter.type() == QContactFilter::UnionFilter) {
+        QList<QContactFilter> filters = ((QContactUnionFilter) filter).filters();
+        for(int i(0); filterSupportedFlag && i < filters.count(); i++) {
+            if(result.isEmpty())
+                result = contacts(filters[i], sortOrders, filterSupportedFlag, error);
+            else
+                result = (contacts(filters[i], sortOrders, filterSupportedFlag, error).toSet() + result.toSet()).toList();
+        }
+    // Detail filter with a string list is split and re-constructed into
+    // an intersection filter
+    } else if (filter.type() == QContactFilter::ContactDetailFilter
+            && (static_cast<const QContactDetailFilter &>(filter)).value().type() == QVariant::StringList) {
+        QStringList values = (static_cast<const QContactDetailFilter &>(filter)).value().toStringList();
+        QContactIntersectionFilter intersectionFilter;
+        foreach(QString value, values) {
+            QContactDetailFilter detailFilter = filter;
+            detailFilter.setValue(value);
+            intersectionFilter.append(detailFilter);
+        }
+        // The resulting filter is handled with a recursive function call
+        result = contacts(intersectionFilter, sortOrders, filterSupportedFlag, error);
+    } else {
+        if (filterSupportLevel(filter) == Supported) {
+            filterSupportedFlag = true;
+            // Filter supported, use as the result directly
+            result = filterContacts(filter, error);
+        } else if (filterSupportLevel(filter) == SupportedPreFilterOnly) {
+            // Filter only does pre-filtering, the caller is responsible of
+            // removing possible false positives after filtering
+            filterSupportedFlag = false;
+            result = filterContacts(filter, error);
+        } else {
+            // Don't do filtering here, return all contact ids and tell the
+            // caller to do slow filtering
+            filterSupportedFlag = false;
+            result = filterContacts(QContactInvalidFilter(), error);
+        }
+    }
+
+    return result;
+}
+
+/*!
+ * The contact database version implementation for
+ * QContactManager::filterSupport function.
+*/
+bool CntSymbianFilter::filterSupported(const QContactFilter& filter)
+{
+    TBool result;
+
+    // Map filter support into a boolean value
+    FilterSupport support = filterSupportLevel(filter);
+    if (support == Supported || support == SupportedPreFilterOnly) {
+        result = true;
+    } else {
+        result = false;
+    }
+    return result;
+}
+
+/*!
+ * The possible return values are Supported, NotSupported and
+ * SupportedPreFilterOnly.
+ *
+ * Supported means that the filtering is implemented directly by the underlying
+ * database. NotSupported means that CntSymbianFilter::contacts will
+ * return an error. And SupportedPreFilterOnly means that the filter is not
+ * fully supported, but the CntSymbianFilter::contacts will act like the
+ * filter was supported for performance reasons, returning a result that may
+ * contain false positives. This means that the client must filter the
+ * pre-filtered set of contacts to see if there are false positives included.
+ * Note that the pre-filtering does not give any performance benefits if the
+ * result contains all contacts or almost all contacts.
+ *
+ * \a filter The QContactFilter to be checked.
+ * \a return Supported in case the filter is supported. NotSupported in case
+ * the filter is not supported. returns
+ *
+ * SupportedPreFilterOnly is returned in the following cases:
+ * 1. matchFlags is set to QContactFilter::MatchExactly (CntSymbianFilter::contacts
+ * will use QContactFilter::MatchContains)
+ * 2. matchFlags is set to QContactFilter::MatchStartsWith (CntSymbianFilter::contacts
+ * will use QContactFilter::MatchContains)
+ * 3. matchFlags is set to QContactFilter::MatchEndsWith (CntSymbianFilter::contacts
+ * will use QContactFilter::MatchContains)
+ * 4. matchFlags is set to QContactFilter::MatchCaseSensitive (CntSymbianFilter::contacts
+ * will use QContactFilter::MatchContains)
+ */
+CntAbstractContactFilter::FilterSupport CntSymbianFilter::filterSupportLevel(const QContactFilter& filter)
+{
+    if (filter.type() == QContactFilter::ContactDetailFilter) {
+        const QContactDetailFilter &detailFilter = static_cast<const QContactDetailFilter &>(filter);
+        QContactFilter::MatchFlags matchFlags = detailFilter.matchFlags();
+        const QString defName = detailFilter.detailDefinitionName();
+        const QString fieldName = detailFilter.detailFieldName();
+        
+        // Filter must target a certain field
+        if (fieldName.isEmpty()) {
+            return NotSupported;
+        }         
+
+        // Phone numbers
+        if (defName == QContactPhoneNumber::DefinitionName) {
+            
+            if (matchFlags == QContactFilter::MatchPhoneNumber) {
+                return Supported;
+            }
+            
+            if (matchFlags == QContactFilter::MatchExactly ||
+                matchFlags == QContactFilter::MatchEndsWith ||
+                matchFlags == QContactFilter::MatchFixedString) {
+                return SupportedPreFilterOnly;
+            }
+        // Names
+        } else if (defName == QContactName::DefinitionName
+                || defName == QContactNickname::DefinitionName
+                || defName == QContactEmailAddress::DefinitionName) {
+
+            if (matchFlags == QContactFilter::MatchContains) {
+                return Supported;
+            }
+
+            // Don't care about case sensitivity flag because: 
+            // 1) We do not support it. 2) We are doing prefiltering only.
+            matchFlags &= ~QContactFilter::MatchFlags(QContactFilter::MatchCaseSensitive);
+            
+            if (matchFlags == QContactFilter::MatchExactly ||
+                matchFlags == QContactFilter::MatchContains ||
+                matchFlags == QContactFilter::MatchStartsWith ||
+                matchFlags == QContactFilter::MatchEndsWith ||
+                matchFlags == QContactFilter::MatchFixedString) {
+                return SupportedPreFilterOnly;
+            }            
+        // display label, this is a special case that contains several name
+        // fields and company name
+        //TODO: "unnamed" display label is not supported currently
+        } else if (defName == QContactDisplayLabel::DefinitionName) {
+            
+            if (matchFlags == QContactFilter::MatchStartsWith) {
+                return Supported;
+            }
+            
+            // Don't care about case sensitivity flag because: 
+            // 1) We do not support it. 2) We are doing prefiltering only.
+            matchFlags &= ~QContactFilter::MatchFlags(QContactFilter::MatchCaseSensitive);
+            
+            if (matchFlags == QContactFilter::MatchStartsWith) {
+                return SupportedPreFilterOnly;
+            }              
+        }
+    }
+    return NotSupported;
+}
+
+QList<QContactLocalId> CntSymbianFilter::filterContacts(
+    const QContactFilter& filter,
+    QContactManager::Error& error)
+{
+    QList<QContactLocalId> matches;
+    CContactIdArray* idArray(0);
+
+    if (filter.type() == QContactFilter::InvalidFilter ){
+        TTime epoch(0);
+        idArray = m_contactDatabase.ContactsChangedSinceL(epoch); // return all contacts
+    } else if(filterSupportLevel(filter) == NotSupported) {
+        error = QContactManager::NotSupportedError;
+    } else if (filter.type() == QContactFilter::ContactDetailFilter) {
+        const QContactDetailFilter &detailFilter = static_cast<const QContactDetailFilter &>(filter);
+
+        // Phone numbers
+        if (detailFilter.detailDefinitionName() == QContactPhoneNumber::DefinitionName) {
+            QString number((detailFilter.value()).toString());
+            TPtrC commPtr(reinterpret_cast<const TUint16*>(number.utf16()));
+
+            TInt matchLength(KDefaultMatchLength);
+            // no need to propagate error, we can use the default match length
+            TRAP_IGNORE(getMatchLengthL(matchLength));
+
+            TInt err = matchContacts(idArray, commPtr, matchLength);
+            if(err != KErrNone) {
+                CntSymbianTransformError::transformError(err, error);
+            }
+        // Names, e-mail, display label (other flags)
+        } else {
+            QString name((detailFilter.value()).toString());
+            TPtrC namePtr(reinterpret_cast<const TUint16*>(name.utf16()));
+            CContactItemFieldDef *fieldDef(0);
+            TRAPD(err, transformDetailFilterL(detailFilter, fieldDef));
+            if(err != KErrNone){
+                CntSymbianTransformError::transformError(err, error);
+            } else {
+                Q_ASSERT_X(fieldDef->Count() > 0, "CntSymbianFilter", "Illegal field def");
+                TInt err = findContacts(idArray, *fieldDef, namePtr);
+                if(err != KErrNone) {
+                    CntSymbianTransformError::transformError(err, error);
+                }
+
+                // Display label special case with "starts with" flag;
+                // False positives are removed here for performance reasons
+                // (this is a very common use case in a name list view)
+                //
+                // Another option might be to use CContactDatabase::FindInTextDefLC
+                // for filtering with display label
+                if (detailFilter.detailDefinitionName() == QContactDisplayLabel::DefinitionName
+                    && detailFilter.matchFlags() == QContactFilter::MatchStartsWith) {
+
+                    // Remove false positives
+                    for(TInt i(0); i < idArray->Count(); i++) {
+                        CContactItem* contactItem = m_contactDatabase.ReadContactLC((*idArray)[i]);
+                        const CContactItemFieldSet& fieldSet(contactItem->CardFields());
+                        if(isFalsePositive(fieldSet, KUidContactFieldGivenName, namePtr)
+                           && isFalsePositive(fieldSet, KUidContactFieldFamilyName, namePtr)
+                           && isFalsePositive(fieldSet, KUidContactFieldCompanyName, namePtr)
+                           && isFalsePositive(fieldSet, KUidContactFieldSecondName, namePtr)){
+                            idArray->Remove(i);
+                            i--;
+                        }
+                        CleanupStack::PopAndDestroy(contactItem);
+                    }
+                }
+            }
+            delete fieldDef;
+        }
+    }
+
+    if(idArray && (error == QContactManager::NoError)) {
+        // copy the matching contact ids
+        for(int i(0); i < idArray->Count(); i++) {
+            matches.append(QContactLocalId((*idArray)[i]));
+        }
+    }
+
+    delete idArray;
+
+    return matches;
+}
+
+/*!
+ * Checks if the contact's field set includes field \a fieldTypeUid and if the
+ * field contents matches the search string. The features currently:
+ * 1. only checks the first field instance
+ * 2. supports only "starts with"
+ * 3. searches every word inside the field
+ * 4. supports only "not case-sensitive" matching
+ */
+bool CntSymbianFilter::isFalsePositive(const CContactItemFieldSet& fieldSet, const TUid& fieldTypeUid, const TDesC& searchString)
+{
+    bool value(true);
+    TInt index = fieldSet.Find(fieldTypeUid);
+    if(index >= 0) {
+        const CContactItemField& field(fieldSet[index]);
+        CContactTextField* storage = field.TextStorage();
+        TPtrC text = storage->Text();
+        index = text.FindC(searchString);
+        // Check if this is the first word beginning with search string
+        if(index == 0)
+            value = false;
+        // Check if this is in the beginning of a word (the preceeding
+        // character is a space)
+        else if(index > 0 && TChar(text[index-1]) == TChar(0x20))
+            value = false;
+    }
+    return value;
+}
+
+/*!
+ * Transform detail filter into a contact item field definition that can be
+ * used with CContactDatabase finds.
+ */
+void CntSymbianFilter::transformDetailFilterL(
+        const QContactDetailFilter &detailFilter,
+        CContactItemFieldDef *&fieldDef)
+{
+    const TInt defaultReserve(1);
+    const TInt nameFieldsCount(5);
+
+    CContactItemFieldDef* tempFieldDef = new (ELeave) CContactItemFieldDef();
+    CleanupStack::PushL(tempFieldDef);
+    tempFieldDef->SetReserveL(defaultReserve);
+
+    // TODO: Refactor to use the transform classes
+    // Names
+    if(detailFilter.detailDefinitionName() == QContactName::DefinitionName) {
+        if(detailFilter.detailFieldName() == QContactName::FieldPrefix) {
+            tempFieldDef->AppendL(KUidContactFieldPrefixName);
+        } else if(detailFilter.detailFieldName() == QContactName::FieldFirst) {
+            tempFieldDef->AppendL(KUidContactFieldGivenName);
+        } else if(detailFilter.detailFieldName() == QContactName::FieldMiddle) {
+            tempFieldDef->AppendL(KUidContactFieldAdditionalName);
+        } else if(detailFilter.detailFieldName() == QContactName::FieldLast) {
+            tempFieldDef->AppendL(KUidContactFieldFamilyName);
+        } else if(detailFilter.detailFieldName() == QContactName::FieldSuffix) {
+            tempFieldDef->AppendL(KUidContactFieldSuffixName);
+        } else {
+            // default to all name fields
+            tempFieldDef->SetReserveL(nameFieldsCount);
+            tempFieldDef->AppendL(KUidContactFieldPrefixName);
+            tempFieldDef->AppendL(KUidContactFieldGivenName);
+            tempFieldDef->AppendL(KUidContactFieldAdditionalName);
+            tempFieldDef->AppendL(KUidContactFieldFamilyName);
+            tempFieldDef->AppendL(KUidContactFieldSuffixName);
+        }
+    }
+    // Nick name
+    else if(detailFilter.detailDefinitionName() == QContactNickname::DefinitionName) {
+        tempFieldDef->AppendL(KUidContactFieldSecondName);
+    }
+    // Email
+    else if(detailFilter.detailDefinitionName() == QContactEmailAddress::DefinitionName) {
+        tempFieldDef->AppendL(KUidContactFieldEMail);
+    }
+    // Display label
+    else if(detailFilter.detailDefinitionName() == QContactDisplayLabel::DefinitionName) {
+        // in S60 display label is constructed with "nick name", "first name",
+        // "last name" and/or "company name"
+        tempFieldDef->SetReserveL(nameFieldsCount);
+        tempFieldDef->AppendL(KUidContactFieldSecondName);
+        tempFieldDef->AppendL(KUidContactFieldGivenName);
+        tempFieldDef->AppendL(KUidContactFieldFamilyName);
+        tempFieldDef->AppendL(KUidContactFieldCompanyName);
+    }
+
+    CleanupStack::Pop(tempFieldDef);
+    fieldDef = tempFieldDef;
+}
+
+/*!
+ * Find contacts based on a contact field contents.
+ * \a idArray On return contains the ids of the contacts that have the field
+ * defined that contains the find text.
+ * \a fieldUid The UID of the contact database field to be searched.
+ * \a text The text to be searched for.
+ * \return Symbian error code.
+ */
+TInt CntSymbianFilter::findContacts(
+        CContactIdArray*& idArray,
+        const CContactItemFieldDef& fieldDef,
+        const TDesC& text) const
+{
+    CContactIdArray* idArrayTmp(0);
+    TRAPD( err, idArrayTmp = findContactsL(fieldDef, text));
+    if(err == KErrNone)
+    {
+        idArray = idArrayTmp;
+    }
+    return err;
+}
+
+/*!
+ * Leaving implementation called by findContacts.
+ */
+CContactIdArray* CntSymbianFilter::findContactsL(
+        const CContactItemFieldDef& fieldDef,
+        const TDesC& text) const
+{
+    CContactIdArray* idsArray = m_contactDatabase.FindLC(text, &fieldDef);
+    CleanupStack::Pop(idsArray); // Ownership transferred
+    return idsArray;
+}
+
+/*
+ * Find contacts based on a phone number.
+ * \a idArray On return contains the ids of the contacts that match the filter.
+ * \a phoneNumber The phone number to match
+ * \a matchLength Match length; digits from right.
+ */
+TInt CntSymbianFilter::matchContacts(
+        CContactIdArray*& idArray,
+        const TDesC& phoneNumber,
+        const TInt matchLength)
+{
+    CContactIdArray* idArrayTmp(0);
+    TRAPD( err, idArrayTmp = m_contactDatabase.MatchPhoneNumberL(phoneNumber, matchLength));
+    if(err == KErrNone)
+    {
+        idArray = idArrayTmp;
+    }
+    return err;
+}
+
+/*
+ * Get the match length setting used in MatchPhoneNumber type filtering.
+ * \a matchLength Phone number digits to be used in matching (counted from
+ * right).
+ */
+void CntSymbianFilter::getMatchLengthL(TInt& matchLength)
+{
+    //Get number of digits used to match
+    CRepository* repository = CRepository::NewL(KCRUidTelConfiguration);
+    CleanupStack::PushL(repository);
+    User::LeaveIfError(repository->Get(KTelMatchDigits, matchLength));
+    CleanupStack::PopAndDestroy(repository);
+}
+
+#endif