plugins/contacts/symbiansim/src/cntsimstoreprivate.cpp
changeset 0 876b1a06bc25
child 5 603d3f8b6302
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/contacts/symbiansim/src/cntsimstoreprivate.cpp	Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,811 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $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$
+**
+****************************************************************************/
+
+#include "cntsimstoreprivate.h"
+#include "cntsymbiansimtransformerror.h"
+#include "cntsimstore.h"
+#include "cntsimstoreeventlistener.h"
+
+#include <mmtsy_names.h>
+#include <qtcontacts.h>
+#include <qcontactchangeset.h>
+#include <QDebug>
+
+const TInt KOneSimContactBufferSize = 512;
+const TInt KDataClientBuf  = 128;
+const TInt KEtsiTonPosition = 0x70;
+
+CntSimStorePrivate* CntSimStorePrivate::NewL(CntSymbianSimEngine &engine, CntSimStore &simStore, const QString &storeName)
+{
+    CntSimStorePrivate* self = new (ELeave) CntSimStorePrivate(engine, simStore, storeName);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    CleanupStack::Pop(self);
+    return self;
+}
+
+CntSimStorePrivate::CntSimStorePrivate(CntSymbianSimEngine &engine, CntSimStore &simStore, const QString &storeName)
+    :CActive(CActive::EPriorityStandard),
+     m_state(InactiveState),
+     m_engine(engine),
+     m_simStore(simStore),
+     m_listener(0),
+     m_extraDetailsChecked(false)
+{
+    CActiveScheduler::Add(this);
+    m_managerUri = engine.managerUri();
+    
+    // Initialize store info
+    m_storeInfo.m_storeName                 = storeName;
+    m_storeInfo.m_totalEntries              = -1;
+    m_storeInfo.m_usedEntries               = -1;
+    m_storeInfo.m_readOnlyAccess            = false; 
+    m_storeInfo.m_numberSupported           = true; // allways supported
+    m_storeInfo.m_nameSupported             = true; // allways supported
+    m_storeInfo.m_secondNameSupported       = false;
+    m_storeInfo.m_additionalNumberSupported = false;
+    m_storeInfo.m_emailSupported            = false;
+    
+    // SDN store is allways read only
+    if (m_storeInfo.m_storeName == KParameterValueSimStoreNameSdn)
+        m_storeInfo.m_readOnlyAccess = true;
+}
+
+void CntSimStorePrivate::ConstructL()
+{
+    TBuf<RMobilePhoneBookStore::KMaxPBIDSize> storeName;
+    convertStoreNameL(storeName);
+    
+    // Open etel server
+    User::LeaveIfError(m_etelServer.Connect());
+    User::LeaveIfError(m_etelServer.LoadPhoneModule(KMmTsyModuleName));
+
+    // Open etel phone
+    RTelServer::TPhoneInfo info;
+    User::LeaveIfError(m_etelServer.GetPhoneInfo(0, info));
+    User::LeaveIfError(m_etelPhone.Open(m_etelServer, info.iName));
+    
+    // Open Etel store
+    User::LeaveIfError(m_etelStore.Open(m_etelPhone, storeName));
+
+    // Update store info
+    updateStoreInfoL();
+    
+    // Start listening for events
+    m_listener = new (ELeave) CntSimStoreEventListener(m_engine, m_etelStore);
+    m_listener->start();
+}
+
+CntSimStorePrivate::~CntSimStorePrivate()
+{
+    Cancel();
+    delete m_listener;
+    m_etelStore.Close();
+    m_etelPhone.Close();
+    m_etelServer.Close();
+}
+
+void CntSimStorePrivate::convertStoreNameL(TDes &storeName)
+{
+    if(storeName.MaxLength() < RMobilePhoneBookStore::KMaxPBIDSize) {
+        User::Leave(KErrArgument);
+    }
+
+    if (m_storeInfo.m_storeName.isEmpty()) {
+        // Default to ADN store
+        m_storeInfo.m_storeName = (QLatin1String) KParameterValueSimStoreNameAdn;
+        storeName.Copy(KETelIccAdnPhoneBook);
+    } else if (m_storeInfo.m_storeName == KParameterValueSimStoreNameFdn) {
+        storeName.Copy(KETelIccFdnPhoneBook);
+    } else if (m_storeInfo.m_storeName == KParameterValueSimStoreNameAdn) {
+        storeName.Copy(KETelIccAdnPhoneBook);
+    } else if (m_storeInfo.m_storeName == KParameterValueSimStoreNameSdn) {
+        storeName.Copy(KETelIccSdnPhoneBook);
+    }
+
+    // Check that we got a valid store name
+    if(storeName.Length() == 0) {
+        User::Leave(KErrArgument);
+    }
+}
+
+bool CntSimStorePrivate::read(int index, int numSlots, QContactManager::Error *error)
+{
+    if (IsActive()) {
+        *error = QContactManager::LockedError;
+        return false;
+    }
+    
+    // start reading
+    m_buffer.Zero();
+    m_buffer.ReAlloc(KOneSimContactBufferSize*numSlots);
+    m_etelStore.Read(iStatus, index, numSlots, m_buffer);
+    SetActive();
+    m_state = ReadState;
+    
+    *error = QContactManager::NoError;
+    return true;
+}
+
+bool CntSimStorePrivate::write(const QContact &contact, QContactManager::Error *error)
+{
+    if (IsActive()) {
+        *error = QContactManager::LockedError;
+        return false;
+    }
+    
+    // get index
+    m_writeIndex = KErrNotFound;
+    if (contact.id().managerUri() == m_managerUri &&
+        contact.localId() > 0) {
+        m_writeIndex = contact.localId();
+
+        // TODO: check that the contact exist in the sim 
+    }
+    
+    // encode
+    m_buffer.Zero();
+    m_buffer.ReAlloc(KOneSimContactBufferSize);
+    m_convertedContact = QContact(contact);
+
+    TRAPD(err, encodeSimContactL(&m_convertedContact, m_buffer));
+    if (err != KErrNone) {
+        CntSymbianSimTransformError::transformError(err, error);
+        return false;
+    }
+
+    // start writing
+    m_etelStore.Write(iStatus, m_buffer, m_writeIndex);
+    SetActive();
+    m_state = WriteState;
+    
+    *error = QContactManager::NoError;
+    return true;
+}
+
+bool CntSimStorePrivate::remove(int index, QContactManager::Error *error)
+{
+    if (IsActive()) {
+        *error = QContactManager::LockedError;
+        return false;
+    }
+    
+    // NOTE:
+    // If index points to an empty slot and running in hardware the 
+    // delete operation will not return any error.
+    
+    m_etelStore.Delete(iStatus, index);
+    SetActive();
+    m_state = DeleteState;
+    
+    *error = QContactManager::NoError;
+    return true;
+}
+
+bool CntSimStorePrivate::getReservedSlots(QContactManager::Error *error)
+{
+    if (IsActive()) {
+        *error = QContactManager::LockedError;
+        return false;
+    }
+    
+    // start reading
+    m_buffer.Zero();
+    m_buffer.ReAlloc(KOneSimContactBufferSize*m_storeInfo.m_totalEntries);
+    m_etelStore.Read(iStatus, 1, m_storeInfo.m_totalEntries, m_buffer);
+    SetActive();
+    m_state = ReadReservedSlotsState;
+    
+    *error = QContactManager::NoError;
+    return true;  
+}
+
+void CntSimStorePrivate::DoCancel()
+{
+    if (m_state == ReadState)
+        m_etelStore.CancelAsyncRequest(EMobilePhoneStoreRead);
+    if (m_state == WriteState)
+        m_etelStore.CancelAsyncRequest(EMobilePhoneStoreWrite);
+    if (m_state == DeleteState)
+        m_etelStore.CancelAsyncRequest(EMobilePhoneStoreDelete);
+    if (m_state == ReadReservedSlotsState)
+        m_etelStore.CancelAsyncRequest(EMobilePhoneStoreRead);
+    
+    m_state = InactiveState;
+}
+
+void CntSimStorePrivate::RunL()
+{
+    //qDebug() << "CntSimStorePrivate::RunL()" << m_state << iStatus.Int();
+    
+    m_asyncError = iStatus.Int();
+    User::LeaveIfError(iStatus.Int());
+    
+    // NOTE: It is assumed that emitting signals is queued
+    
+    switch (m_state)
+    {
+        case ReadState:
+        {
+            QList<QContact> contacts = decodeSimContactsL(m_buffer);
+
+            // set sync target and read only access constraint and display label
+            QList<QContact>::iterator i;
+            for (i = contacts.begin(); i != contacts.end(); ++i) {
+                QContactSyncTarget syncTarget;
+                syncTarget.setSyncTarget(KSimSyncTarget);
+                m_engine.setReadOnlyAccessConstraint(&syncTarget);
+                i->saveDetail(&syncTarget);
+                QContactType contactType = i->detail(QContactType::DefinitionName);
+                m_engine.setReadOnlyAccessConstraint(&contactType);
+                i->saveDetail(&contactType);
+                m_engine.updateDisplayLabel(*i);
+            }
+            
+            emit m_simStore.readComplete(contacts, QContactManager::NoError);
+        }
+        break;
+        
+        case WriteState:
+        {
+            // save id
+            QContactId contactId;
+            contactId.setLocalId(m_writeIndex);
+            contactId.setManagerUri(m_managerUri);
+            m_convertedContact.setId(contactId);  
+            
+            // set sync target
+            if(m_convertedContact.detail(QContactSyncTarget::DefinitionName).isEmpty()) {
+                QContactSyncTarget syncTarget = m_convertedContact.detail(QContactSyncTarget::DefinitionName);
+                syncTarget.setSyncTarget(KSimSyncTarget);
+                m_engine.setReadOnlyAccessConstraint(&syncTarget);
+                m_convertedContact.saveDetail(&syncTarget);
+            }
+
+            // set type as read only
+            QContactType contactType = m_convertedContact.detail(QContactType::DefinitionName);
+            m_engine.setReadOnlyAccessConstraint(&contactType);
+            m_convertedContact.saveDetail(&contactType);
+
+            emit m_simStore.writeComplete(m_convertedContact, QContactManager::NoError);
+        }
+        break;
+        
+        case DeleteState:
+        {
+            emit m_simStore.removeComplete(QContactManager::NoError);
+        }
+        break;
+        
+        case ReadReservedSlotsState:
+        {
+            QList<int> reservedSlots = decodeReservedSlotsL(m_buffer);
+            emit m_simStore.getReservedSlotsComplete(reservedSlots, QContactManager::NoError);
+        }
+        break;
+        
+        default:
+        {
+            User::Leave(KErrUnknown);
+        }
+        break;
+    }
+    m_state = InactiveState;
+}
+
+TInt CntSimStorePrivate::RunError(TInt aError)
+{
+    QContactManager::Error qtError = QContactManager::NoError;
+    CntSymbianSimTransformError::transformError(aError, &qtError);
+    
+    // NOTE: It is assumed that emitting signals is queued
+        
+    if (m_state == ReadState) 
+        emit m_simStore.readComplete(QList<QContact>(), qtError);            
+    else if (m_state == WriteState)
+        emit m_simStore.writeComplete(m_convertedContact, qtError);  
+     else if (m_state == DeleteState)
+        emit m_simStore.removeComplete(qtError);
+    else if (m_state == ReadReservedSlotsState)
+        emit m_simStore.getReservedSlotsComplete(QList<int>(), qtError);
+        
+    m_state = InactiveState;
+    
+    return KErrNone;
+}
+
+/*! 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
+
+    CleanupStack::PopAndDestroy(pbBuffer);
+    PbkPrintToLog(_L("CntSymbianSimEngine::decodeSimContactsL() - OUT"));
+    return fetchedContacts;
+}
+
+/*! Converts QContact to the TLV format which is used to save it to the Etel store.
+ *
+ * \param contact QContact to be converted
+ * \param rawData Contact in TLV format on return.
+*/
+void CntSimStorePrivate::encodeSimContactL(QContact* contact, TDes8& rawData) const
+{
+    // Keep track of the count of phone numbers added
+    int phoneNumberCount(0);
+
+    // Create buffer
+    CPhoneBookBuffer* pbBuffer = new(ELeave) CPhoneBookBuffer();
+    CleanupStack::PushL(pbBuffer);
+    pbBuffer->Set(&rawData);
+    User::LeaveIfError(pbBuffer->AddNewEntryTag());
+
+    // Loop through details
+    foreach(QContactDetail detail, contact->details()) 
+    {
+        QString definitionName = detail.definitionName();
+        
+        // NOTE: If the detail is too long let the etel store return
+        // an error about it. We could check the maximum lenghts for each
+        // detail but then we would need to read it before every write operation
+        // bacause it seems to change depending on how full the sim card is.
+
+        // Name
+        if (definitionName == QContactName::DefinitionName) 
+        {
+            QContactName nameDetail = static_cast<QContactName>(detail);
+            QString name = nameDetail.customLabel();
+            putTagAndValueL(pbBuffer, RMobilePhoneBookStore::ETagPBText, name);
+        }
+        // Phone number
+        else if (definitionName == QContactPhoneNumber::DefinitionName)
+        {
+            if (m_storeInfo.m_additionalNumberSupported == false && phoneNumberCount>0)
+                User::Leave(KErrNotSupported);
+
+            phoneNumberCount++;
+            QString number = static_cast<QContactPhoneNumber>(detail).number();
+            if (number.isEmpty())
+                continue;
+
+            // Verify the number only contains legal digits
+            foreach (const QChar character, number) {
+                if(!character.isDigit()) {
+                    if(character != QChar('+')
+                        && character != QChar('*')
+                        && character != QChar('#')
+                        && character != QChar('p')
+                        && character != QChar('w')) {
+                        User::Leave(KErrArgument);
+                    }
+                }
+            }
+
+            if (phoneNumberCount > 1) {
+                // Mark the beginning of an additional number
+                User::LeaveIfError(pbBuffer->AddNewNumberTag());
+            }
+
+            // The number itself
+            putTagAndValueL(pbBuffer, RMobilePhoneBookStore::ETagPBNumber, number);
+        }
+        // Nickname
+        else if (definitionName == QContactNickname::DefinitionName)
+        {
+            if (m_storeInfo.m_secondNameSupported == false)
+                User::Leave(KErrNotSupported);
+            
+            QString nickname = static_cast<QContactNickname>(detail).nickname();
+            if (nickname.isEmpty())
+                continue;
+            
+            putTagAndValueL(pbBuffer, RMobilePhoneBookStore::ETagPBSecondName, nickname);
+        } 
+        // email
+        else if (definitionName == QContactEmailAddress::DefinitionName)
+        {
+            if (m_storeInfo.m_emailSupported == false)
+                User::Leave(KErrNotSupported);
+            
+            QString email = static_cast<QContactEmailAddress>(detail).emailAddress();
+            if (email.isEmpty())
+                continue;            
+            
+            putTagAndValueL(pbBuffer, RMobilePhoneBookStore::ETagPBEmailAddress, email);
+        }
+        // These are ignored in the conversion
+        else if (definitionName == QContactSyncTarget::DefinitionName
+            || definitionName == QContactDisplayLabel::DefinitionName
+            || definitionName == QContactType::DefinitionName) 
+        {
+            // Do nothing
+        } 
+        else 
+        {
+            // Unknown detail
+            User::Leave(KErrArgument);
+        }
+    }
+    CleanupStack::PopAndDestroy(pbBuffer);
+}
+
+void CntSimStorePrivate::putTagAndValueL(CPhoneBookBuffer* pbBuffer, TUint8 tag, QString data) const
+{
+    TPtrC value(reinterpret_cast<const TUint16*>(data.utf16()));
+    User::LeaveIfError(pbBuffer->PutTagAndValue(tag, value));
+}
+
+QList<int> CntSimStorePrivate::decodeReservedSlotsL(TDes8& rawData) const
+{
+    QList<int> reservedSlots;
+
+    TUint8 tagValue(0);
+    CPhoneBookBuffer::TPhBkTagType dataType;
+
+    CPhoneBookBuffer* pbBuffer = new (ELeave) CPhoneBookBuffer();
+    CleanupStack::PushL(pbBuffer);
+    pbBuffer->Set(&rawData);
+    pbBuffer->StartRead();
+
+    while (pbBuffer->GetTagAndType(tagValue, dataType) == KErrNone)
+    {
+        if (tagValue == RMobilePhoneBookStore::ETagPBAdnIndex) 
+        {
+            TUint16 index;
+            if (pbBuffer->GetValue(index) == KErrNone)
+                reservedSlots.append(index);
+        } else
+            pbBuffer->SkipValue(dataType);
+    } //while
+    
+    CleanupStack::PopAndDestroy(pbBuffer);
+    return reservedSlots;
+}
+
+void CntSimStorePrivate::writeL(QContact *contact)
+{
+    if (IsActive())
+        User::Leave(KErrLocked);
+    
+    // get index
+    int index = KErrNotFound;
+    if (contact->id().managerUri() == m_managerUri &&
+        contact->localId() > 0) {
+        index = contact->localId();
+    }
+    
+    // encode
+    m_buffer.Zero();
+    m_buffer.ReAlloc(KOneSimContactBufferSize);
+    encodeSimContactL(contact, m_buffer);
+
+    // write
+    TRequestStatus status;
+    m_etelStore.Write(status, m_buffer, index);
+    User::WaitForRequest(status);
+    User::LeaveIfError(status.Int());
+    
+    // update id
+    QContactId id;
+    id.setLocalId(index);
+    id.setManagerUri(m_managerUri);
+    contact->setId(id);
+}
+
+void CntSimStorePrivate::removeL(int index)
+{
+    if (IsActive())
+        User::Leave(KErrLocked);
+    
+    // NOTE:
+    // If index points to an empty slot and running in hardware the 
+    // delete operation will not return any error.
+    
+    TRequestStatus status;
+    m_etelStore.Delete(status, index);
+    User::WaitForRequest(status);
+    User::LeaveIfError(status.Int());
+}
+
+void CntSimStorePrivate::updateStoreInfoL()
+{
+#ifdef SYMBIANSIM_BACKEND_PHONEBOOKINFOV1
+    RMobilePhoneBookStore::TMobilePhoneBookInfoV1 info;
+    RMobilePhoneBookStore::TMobilePhoneBookInfoV1Pckg infoPckg(info);
+#else
+    RMobilePhoneBookStore::TMobilePhoneBookInfoV5 info;
+    RMobilePhoneBookStore::TMobilePhoneBookInfoV5Pckg infoPckg(info);
+#endif
+
+    // Get info
+    TRequestStatus status;
+    m_etelStore.GetInfo(status, infoPckg);
+    User::WaitForRequest(status);
+    User::LeaveIfError(status.Int());
+
+    // Update entry counts
+    m_storeInfo.m_totalEntries = info.iTotalEntries;
+    m_storeInfo.m_usedEntries  = info.iUsedEntries;
+    
+#ifdef SYMBIANSIM_BACKEND_TEST_EXTRADETAILS
+    // Check if store supports the extra details
+    //
+    // NOTE: 
+    // We cannot rely on TMobilePhoneBookInfoV5 to check if we support
+    // these details. For example iMaxSecondNames is allways -1 even if the sim
+    // card supports a second name.
+    //
+    // There is an API for checking these but it's Nokia internal so we must
+    // do it this way - by checking if saving these details is possible.
+    
+    // Have we checked these already?
+    if (m_extraDetailsChecked == false)
+    {
+        // Cannot test extra details if sim card is full 
+        if (m_storeInfo.m_usedEntries == m_storeInfo.m_totalEntries)
+            return;
+
+        // Cancel store event listener temporarily
+        if (m_listener)
+            m_listener->Cancel();
+
+        // Test writing nickname
+        QContact contact;
+        QContactNickname nick;
+        nick.setNickname("simbackend test");
+        contact.saveDetail(&nick);
+        TRAPD(err, {
+            m_storeInfo.m_secondNameSupported = true; // enable to pass encodeSimContactL()
+            writeL(&contact);
+            removeL(contact.localId());
+        } );
+        if (err)
+            m_storeInfo.m_secondNameSupported = false;
+
+        // Test writing additional number
+        contact = QContact();
+        QContactPhoneNumber num1;
+        num1.setNumber("1111111111");
+        contact.saveDetail(&num1);
+        QContactPhoneNumber num2;
+        num2.setNumber("2222222222");
+        contact.saveDetail(&num2);
+        TRAP(err, {
+            m_storeInfo.m_additionalNumberSupported = true; // enable to pass encodeSimContactL()
+            writeL(&contact); 
+            removeL(contact.localId()); 
+        } );
+        if (err)
+            m_storeInfo.m_additionalNumberSupported = false;
+
+        // Test writing email
+        contact = QContact();
+        QContactEmailAddress email;
+        email.setEmailAddress("simbackend@test.com");
+        contact.saveDetail(&email);
+        TRAP(err, {
+            m_storeInfo.m_emailSupported = true; // enable to pass encodeSimContactL()
+            writeL(&contact); 
+            removeL(contact.localId()); 
+        } );
+        if (err)
+            m_storeInfo.m_emailSupported = false;
+
+        // Start store event listener again
+        if (m_listener)
+            m_listener->start();
+
+        m_extraDetailsChecked = true;
+    }
+#endif
+
+    /*
+    qDebug() << "Store info:"
+        << "\nStore name                :" << m_storeInfo.m_storeName
+        << "\nTotal entries             :" << m_storeInfo.m_totalEntries
+        << "\nUsed entries              :" << m_storeInfo.m_usedEntries
+        << "\nRead only access          :" << m_storeInfo.m_readOnlyAccess
+        << "\nNumber supported          :" << m_storeInfo.m_numberSupported
+        << "\nName supported            :" << m_storeInfo.m_nameSupported
+        << "\nSecond name supported     :" << m_storeInfo.m_secondNameSupported
+        << "\nAdditional name supported :" << m_storeInfo.m_additionalNumberSupported
+        << "\nEmail supported           :" << m_storeInfo.m_emailSupported;
+    */
+}