wvuing/wvuieng/EngSrc/CCAGroupManager.cpp
changeset 0 094583676ce7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wvuing/wvuieng/EngSrc/CCAGroupManager.cpp	Thu Dec 17 08:41:52 2009 +0200
@@ -0,0 +1,1991 @@
+/*
+* Copyright (c) 2002-2005 Nokia Corporation and/or its subsidiary(-ies). 
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:  Network operations for chat group handling.
+ *
+*/
+
+// INCLUDE FILES
+#include "ChatDebugPrint.h"
+
+#include "CCAGroupManager.h"
+
+#include "CCAStorageManagerFactory.h"
+#include "MCASettings.h"
+#include "MCASearchData.h"
+#include "MCASearchInterface.h"
+#include "MCAStoredGroups.h"
+#include "MCAStoredGroup.h"
+#include "MCAExtendedStoredGroup.h"
+#include "MCAGroupOperations.h"
+#include "CCAGroupWrapper.h"
+#include "PrivateEngineDefinitions.h"
+#include "TStorageManagerGlobals.h"
+#include "CCARequest.h"
+#include "CCARequestMapper.h"
+#include "MCAChatInterface.h"
+#include "MCAMessagesWriteInterface.h"
+#include "MCAMessageUtils.h"
+#include "MCAMessageCreator.h"
+#include "ImpsCSPAllErrors.h"
+#include "MCAImpsFactory.h"
+
+#include "CAUtils.h"
+#include "camessageutil.h"
+
+#include <e32keys.h>
+#include <ImpsFundamental.h>
+#include <ImpsGroupProps.h>
+
+// for resources
+#include <caengineNG.rsg>
+#include <stringloader.h>
+
+// CONSTANTS
+const TInt KCCAGMGroupWrappersGranularity = 5;
+const TInt KSearchPairsSmallGranularity = 1;
+const TInt KMaxUserSearchLimit = 100;
+
+// indexes to additional data array inside request
+const TInt KReqCreateIndexGroupId    = 0;
+const TInt KReqCreateIndexScreenName = 2;
+const TInt KReqCreateIndexWelcomeMsg = 3;
+const TInt KReqCreateIndexGroupTopic = 4;
+
+const TInt KReqJoinIndexGroupId    = 0;
+const TInt KReqJoinIndexScreenName = 1;
+
+// how many times should we try to generate unique group id
+const TInt KMaxGroupIds = 100;
+
+// maximum groups in search result. If search results
+// exceeds this then "too many search results" is shown
+const TInt KMaxGroupSearchLimit = 50;
+
+
+// ================= MEMBER FUNCTIONS =======================
+
+// Symbian OS default constructor can leave.
+void CCAGroupManager::ConstructL()
+    {
+    iStoredGroups = CCAStorageManagerFactory::GroupListInterfaceL();
+    }
+
+// Two-phased constructor.
+CCAGroupManager* CCAGroupManager::NewL(
+    MCASearchInterface& aSearchAPI,
+    MCASettings& aSettingsAPI,
+    CCARequestMapper& aRequestMapper,
+    MCAImpsFactory* aIMPSFactory,
+    MCAChatInterface& aChatInterface,
+    MCAMessageUtils& aMessageUtils )
+    {
+    CCAGroupManager* self = new ( ELeave ) CCAGroupManager( aSearchAPI,
+                                                            aSettingsAPI, aRequestMapper, aIMPSFactory,
+                                                            aChatInterface, aMessageUtils );
+
+    CleanupStack::PushL( self );
+    self->ConstructL();
+    CleanupStack::Pop( self );
+
+    return self;
+    }
+
+// Destructor
+CCAGroupManager::~CCAGroupManager()
+    {
+    iGroupWrappers.ResetAndDestroy();
+    delete iProperties;
+    delete iPrivProperties;
+    }
+
+// C++ default constructor can NOT contain any code, that
+// might leave.
+//
+CCAGroupManager::CCAGroupManager(
+    MCASearchInterface& aSearchAPI,
+    MCASettings& aSettingsAPI,
+    CCARequestMapper& aRequestMapper,
+    MCAImpsFactory* aIMPSFactory,
+    MCAChatInterface& aChatInterface,
+    MCAMessageUtils& aMessageUtils   )
+        : iRequestMapper( aRequestMapper ),
+        iSettingsAPI( aSettingsAPI ),
+        iSearchAPI( aSearchAPI ),
+        iImpsFactory( aIMPSFactory ),
+        iChatInterface( aChatInterface ),
+        iMessageUtils( aMessageUtils ),
+        iGroupWrappers( KCCAGMGroupWrappersGranularity )
+    {
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::PopulateCreatedGroupsL
+// ---------------------------------------------------------
+//
+TInt CCAGroupManager::PopulateCreatedGroupsL( CDesCArray& aGroupList ) const
+    {
+    CHAT_DP_FUNC_ENTER( "PopulateCreatedGroupsL" );
+
+    // Reset the given array to be sure that it will be valid
+    aGroupList.Reset();
+
+    HBufC* loggedUserId = iSettingsAPI.ValueL( MCASettings::EOwnWVUserID );
+    CleanupStack::PushL( loggedUserId );
+
+    CSearchPairs* pairs =
+        new ( ELeave ) CSearchPairs( KSearchPairsSmallGranularity );
+    CleanupStack::PushL( pairs );
+
+    CImpsSearchRequest* request = CImpsSearchRequest::NewL();
+    CleanupStack::PushL( request );
+
+    request->SetRequestL( EImpsGroupUserIDOwner, *loggedUserId );
+    pairs->AppendL( request );
+
+    // If there is error in search, return so we don't send stop error request
+    TInt searchError( KErrNone );
+    searchError = iSearchAPI.StartSearchL( *pairs, KMaxUserSearchLimit, NULL );
+    if ( searchError != KErrNone )
+        {
+        CHAT_DP( D_CHAT_LIT( "CCAGroupManager::PopulateCreatedGroupsL - error \
+                              from StartSearchL %d" ), searchError );
+        // request, pairs, loggedUserId
+        CleanupStack::PopAndDestroy( 3, loggedUserId );
+        return searchError;
+        }
+
+    TInt resultCount( iSearchAPI.SearchDataInterface()->SearchDataCount() );
+
+    // just add all found groups to the group list
+    for ( TInt counter( 0 ); counter < resultCount; counter++ )
+        {
+        TPtrC pGroupId(
+            iSearchAPI.SearchDataInterface()->SearchData( counter ) );
+        aGroupList.AppendL( pGroupId );
+        }
+
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::PopulateCreatedGroupsL - found %d \
+                          groups from joined user" ), aGroupList.Count() );
+
+    CleanupStack::PopAndDestroy( 3, loggedUserId );
+
+    CHAT_DP_FUNC_DONE( "PopulateCreatedGroupsL" );
+
+    return KErrNone;
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::JoinedGroups
+// ---------------------------------------------------------
+//
+TInt CCAGroupManager::JoinedGroups()
+    {
+    return iNumJoinedGroups;
+    }
+
+
+// we just wrap the Handle*L routines by passing them
+// on to the correct group wrapper
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleErrorL
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleErrorL(
+    TInt aStatus,
+    TInt aOpId,
+    const TDesC* /* aDescription */,
+    const CImpsDetailed* /* aDetailedRes */,
+    TImpsCspIdentifier& /*aCspId*/ )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleErrorL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleErrorL - opid=%d, status=%d" ),
+             aOpId, aStatus );
+
+    CHAT_DP_FUNC_DONE( "HandleErrorL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleCompleteL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleCompleteL(
+    TInt aOpId,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleCompleteL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleCompleteL - opid=%d" ), aOpId );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aOpId );
+
+    if ( ! wrapper )
+        {
+        // no such wrapper
+        CHAT_DP_FUNC_DP( "HandleCompleteL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CCARequest* request = wrapper->Request();
+    __ASSERT_DEBUG( request, User::Panic( KCAEnginePanicCategory,
+                                          KErrNotFound ) );
+    if ( !request )
+        {
+        User::Leave( KErrNotFound );
+        }
+    const MDesCArray* dataArray = request->AdditionalData();
+
+    switch ( request->RequestType() )
+        {
+        case ECreateAndJoinGroup :
+            {
+            __ASSERT_DEBUG( dataArray, User::Panic( KCAEnginePanicCategory,
+                                                    KErrNotFound ) );
+            if ( !dataArray )
+                {
+                // can't use User::LeaveIfNull for const pointers
+                User::Leave( KErrGeneral );
+                }
+
+            CHAT_DP_FUNC_DP( "HandleCompleteL", "Creating chat data for group" );
+
+            MCAMessagesWriteInterface& groupChat =
+                iChatInterface.MessageWriteInterfaceL( KNullDesC, KNullDesC,
+                                                       dataArray->MdcaPoint( KReqCreateIndexGroupId ),
+                                                       MCAMessagesReadInterface::EGroupContainer );
+
+            groupChat.SetScreenNameL( dataArray->MdcaPoint(
+                                          KReqCreateIndexScreenName ) );
+
+            // Write welcome message to chat
+            TPtrC welcome = dataArray->MdcaPoint( KReqCreateIndexWelcomeMsg );
+            TPtrC topic = dataArray->MdcaPoint( KReqCreateIndexGroupTopic );
+
+            CHAT_DP_FUNC_DP( "HandleCompleteL", "Showing welcome message" );
+            MCAStoredGroup& group = wrapper->Group();
+            TPtrC groupIdentifier =
+                group.GroupName().Length() > 0 ? group.GroupName() :
+                CAUtils::DisplayId( group.GroupId() );
+
+            WriteWelcomeMessageL( welcome, groupIdentifier, topic, groupChat );
+
+            // note, it suffices to increase the number of created groups
+            // when CreateGroupL is called
+            break;
+            }
+
+        default:
+            {
+            break;
+            }
+
+        }
+
+    wrapper->HandleCompleteL( aOpId, aCspId );
+
+    CHAT_DP_FUNC_DONE( "HandleCompleteL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleJoinL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleJoinL(
+    TInt aOpId,
+    const MDesCArray& aUserList,
+    const MDesCArray& aScreenNames,
+    const TDesC& aWelcomeText,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleJoinL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleJoinL - opid=%d" ), aOpId );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aOpId );
+
+    if ( ! wrapper )
+        {
+        // no wrapper
+        CHAT_DP_FUNC_DP( "HandleJoinL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CCARequest* request = wrapper->Request();
+    __ASSERT_DEBUG( request, User::Panic( KCAEnginePanicCategory,
+                                          KErrNotFound ) );
+    if ( !request )
+        {
+        User::Leave( KErrNotFound );
+        }
+    const MDesCArray* dataArray = request->AdditionalData();
+
+    __ASSERT_DEBUG( dataArray, User::Panic( KCAEnginePanicCategory,
+                                            KErrNotFound ) );
+    if ( !dataArray )
+        {
+        // can't use User::LeaveIfError for const pointers
+        User::Leave( KErrGeneral );
+        }
+
+    CHAT_DP_FUNC_DP( "HandleJoinL", "Creating chat data for group" );
+
+    MCAMessagesWriteInterface& groupChat =
+        iChatInterface.MessageWriteInterfaceL( KNullDesC, KNullDesC,
+                                               dataArray->MdcaPoint( KReqJoinIndexGroupId ),
+                                               MCAMessagesReadInterface::EGroupContainer );
+    groupChat.SetScreenNameL( dataArray->MdcaPoint( KReqJoinIndexScreenName ) );
+
+
+    CImpsCommonGroupProps* commonProps = NULL;
+    CImpsPrivateGroupProps* privProps = NULL;
+    // ownership is preserved in wrapper
+    wrapper->LocalProperties( commonProps, privProps );
+
+    TPtrC topic( KNullDesC );
+    if ( commonProps )
+        {
+        topic.Set( commonProps->Topic() );
+        }
+
+    // Show welcome message
+    MCAStoredGroup& group = wrapper->Group();
+    TPtrC groupIdentifier =
+        group.GroupName().Length() > 0 ? group.GroupName() :
+        CAUtils::DisplayId( group.GroupId() );
+    TRAPD( err, WriteWelcomeMessageL( aWelcomeText, groupIdentifier, topic, groupChat ) );
+    if ( err == KErrNoMemory )
+        {
+        // Handle no memory error here
+        request->SetErrorCode( err );
+        request->StopWaitIfNeeded();
+        return;
+        }
+    else
+        {
+        User::LeaveIfError( err );
+        }
+
+    // Topic was shown in WriteWelcomeMessageL
+    if ( topic.Length() > 0 )
+        {
+        wrapper->SetTopicShown( ETrue );
+        }
+
+    // make sure it's marked as joined in the UI
+    MCAExtendedStoredGroup& grp =
+        static_cast<MCAExtendedStoredGroup&>( wrapper->Group() );
+    if ( !grp.IsJoined() )
+        {
+        grp.SetJoined( ETrue );
+        iNumJoinedGroups++;
+        }
+
+    CHAT_DP_FUNC_DP( "HandleJoinL", "Passing on to wrapper" );
+    wrapper->HandleJoinL( aOpId, aUserList, aScreenNames,
+                          aWelcomeText, aCspId );
+
+    // show the joined-messages
+    TInt numNames = aScreenNames.MdcaCount();
+    for ( TInt index = 0; index < numNames; index++ )
+        {
+        HBufC* newUser = aScreenNames.MdcaPoint( index ).AllocLC();
+
+        HBufC* finalNewUserText =
+            StringLoader::LoadLC( R_SYSTEM_MESSAGE_JOIN,
+                                  *newUser );
+
+        CHAT_DP_FUNC_DP( "HandleJoinL", "Initiating new users message" );
+
+        MCAMessage* newUserMsg =
+            iMessageUtils.MessageCreator().CreateSystemMessageL(
+                MCAMessage::ESystemMessageNewUsers,
+                *finalNewUserText );
+
+        // Append message
+        TRAPD( err, CAMessageUtil::AppendMessageWithDateStampL(
+                   *newUserMsg,
+                   groupChat,
+                   iMessageUtils.MessageCreator() ) );
+
+        if ( err == KErrNoMemory )
+            {
+            // Handle no memory error here
+            CleanupStack::PopAndDestroy( 2, newUser ); // finalNewUserText, newUser
+            request->SetErrorCode( err );
+            request->StopWaitIfNeeded();
+            return;
+            }
+        else
+            {
+            User::LeaveIfError( err );
+            }
+
+        CleanupStack::PopAndDestroy( 2, newUser ); // finalNewUserText, newUser
+        }
+
+    CHAT_DP_FUNC_DONE( "HandleJoinL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleGroupMembersL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleGroupMembersL(
+    TInt aOpId,
+    const MDesCArray& aUserList,
+    const MDesCArray& aScreenNames,
+    const MDesCArray& aModers,
+    const MDesCArray& aAdmins,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleGroupMembersL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleGroupMembersL - opid=%d" ),
+             aOpId );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aOpId );
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleGroupMembersL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CHAT_DP_FUNC_DP( "HandleGroupMembersL", "Passing on to wrapper" );
+    wrapper->HandleGroupMembersL( aOpId, aUserList, aScreenNames, aModers,
+                                  aAdmins, aCspId );
+
+    CHAT_DP_FUNC_DONE( "HandleGroupMembersL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleGroupPropertiesL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleGroupPropertiesL(
+    TInt aOpId,
+    const TDesC& aGroupId,
+    const CImpsCommonGroupProps& aGroupProps,
+    const CImpsPrivateGroupProps& aOwnProps,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleGroupPropertiesL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleGroupPropertiesL - opid=%d" ),
+             aOpId );
+
+    CCAGroupWrapper* wrapper = NULL;
+    TPtrC groupId( aGroupId );
+
+    if ( aOpId != 0 )
+        {
+        // aGroupId is empty
+        wrapper = FindGroupWrapper( aOpId );
+
+        if ( !wrapper )
+            {
+            CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "No wrapper, leaving" );
+            User::Leave( KErrNotFound );
+            }
+
+        groupId.Set( wrapper->Group().GroupId() );
+        }
+    else
+        {
+        // aGroupId is valid
+        wrapper = FindGroupWrapper( aGroupId );
+        }
+
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CImpsCommonGroupProps* common = NULL;
+    CImpsPrivateGroupProps* priv = NULL;
+
+    // does the wrapper have the properties already?
+    wrapper->LocalProperties( common, priv );
+
+    if ( ( !common ) && ( !priv ) )
+        {
+        CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "No existing properties" );
+
+        // no existing properties, so create new ones
+        common = CImpsCommonGroupProps::NewL();
+        CleanupStack::PushL( common );
+
+        priv = CImpsPrivateGroupProps::NewL();
+        CleanupStack::Pop( common );
+
+        CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "Created new properties" );
+
+        // transfer ownership
+        CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "Setting the properties" );
+        wrapper->SetLocalProperties( common, priv );
+        }
+
+    // set the admin status
+    CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "Setting administrator status" );
+    MCAExtendedStoredGroup& group =
+        static_cast<MCAExtendedStoredGroup&>( wrapper->Group() );
+
+    if ( aOwnProps.Privileges() == EImpsAdmin )
+        {
+        CHAT_DP_FUNC_DP( "HandleGroupPropertiesL",
+                         "User is administrator in group" );
+        group.SetAdmin( ETrue );
+        }
+    else if ( aOwnProps.Privileges() != EImpsUserUndef )
+        {
+        CHAT_DP_FUNC_DP( "HandleGroupPropertiesL",
+                         "User is not administrator in group" );
+        group.SetAdmin( EFalse );
+        }
+
+    if ( aGroupProps.GroupName().Length() > 0 )
+        {
+        if ( aGroupProps.GroupName().CompareC( group.GroupName() ) != 0 )
+            {
+            // group name has changed, so update it and signal UI
+            group.SetGroupNameL( aGroupProps.GroupName() );
+            iStoredGroups->SignalGroupChangedL( group.GroupId() );
+            }
+        }
+    // if the group is stored in persistent storage, save changes to disk also
+    if ( group.StorageType() == TStorageManagerGlobals::EStoragePersistent )
+        {
+        group.SaveChangesL();
+        }
+
+
+    // see if anything changed
+    MCAMessagesWriteInterface& groupChat =
+        iChatInterface.MessageWriteInterfaceL( KNullDesC, KNullDesC, groupId );
+
+    // Ignore empty topics. We must only react to push messages
+    // React also when topic was not shown in HandleJoinL while joining
+    if ( ( ( aOpId == 0 ) && ( aGroupProps.Topic().Length() > 0 ) )
+         || ( !( wrapper->IsTopicShown() ) && ( aOpId == iPropsOpIdWhileJoining ) ) )
+        {
+        // there is a topic, but has it changed?
+
+        if ( ( ( 0 != aGroupProps.Topic().CompareC( common->Topic() ) ) ||
+               !( wrapper->IsTopicShown() ) // Topic changed by user, compare gives 0
+             )
+             && aOpId == 0 )
+            {
+            // Check the group topic again, if length > 0, then display the topic
+            if ( aGroupProps.Topic().Length() > 0 )
+                {
+                // it has changed
+                CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "Topic has changed" );
+
+                // Topic has changed so initiate system message
+                HBufC* newTopic = aGroupProps.Topic().AllocLC();
+
+                HBufC* finalNewTopic =
+                    StringLoader::LoadLC( R_SYSTEM_MESSAGE_TOPIC,
+                                          *newTopic );
+
+                MCAMessage* topicChgMsg =
+                    iMessageUtils.MessageCreator().CreateSystemMessageL(
+                        MCAMessage::ESystemMessageTopicChanged,
+                        *finalNewTopic );
+
+                // Append message
+                CAMessageUtil::AppendMessageWithDateStampL(
+                    *topicChgMsg,
+                    groupChat,
+                    iMessageUtils.MessageCreator() );
+
+                wrapper->SetTopicShown( ETrue );
+
+                CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleGroupPropertiesL - \
+                                  topic is %S" ), finalNewTopic );
+
+                CleanupStack::PopAndDestroy( 2, newTopic );//finalNewTopic, newTopic
+                }
+            }
+        else if ( aGroupProps.Topic().Length() > 0
+                  && aOpId == iPropsOpIdWhileJoining )
+            {
+            // Show topic of group when joining
+            iPropsOpIdWhileJoining = KErrNotFound;
+            HBufC* topicText =
+                StringLoader::LoadLC( R_SYSTEM_MESSAGE_TOPIC_TEXT,
+                                      aGroupProps.Topic() );
+            groupChat.AppendL(
+                iMessageUtils.MessageCreator().CreateSystemMessageL(
+                    MCAMessage::ESystemMessageWelcome, *topicText ) );
+
+            wrapper->SetTopicShown( ETrue );
+            CleanupStack::PopAndDestroy( topicText );
+            }
+        }
+
+    // Private (whispering) state change. We must only react to push messages
+    if ( ( aOpId == 0 ) && ( aGroupProps.IsPrivateAllowed() == EImpsPropYes ) )
+        {
+        // The state is active, but has it changed?
+        if ( aGroupProps.IsPrivateAllowed() != common->IsPrivateAllowed() )
+            {
+            // private flag has changed
+            CHAT_DP_FUNC_DP( "HandleGroupPropertiesL",
+                             "Private messaging turn on" );
+            HBufC* privatMessaging =
+                StringLoader::LoadLC( R_SYSTEM_MESSAGE_PRIVAT_ON );
+
+            MCAMessage* privateChgMsg =
+                iMessageUtils.MessageCreator().CreateSystemMessageL(
+                    MCAMessage::ESystemMessagePrivatChanged,
+                    *privatMessaging );
+
+            // Append message
+            CAMessageUtil::AppendMessageWithDateStampL(
+                *privateChgMsg,
+                groupChat,
+                iMessageUtils.MessageCreator() );
+
+            CleanupStack::PopAndDestroy( privatMessaging );
+            }
+        }
+    else
+        {
+        // we must only react to push messages
+        if ( ( aOpId == 0 ) &&
+             ( aGroupProps.IsPrivateAllowed() != EImpsPropUndef ) )
+            {
+            // the flag is in some non-empty, valid, non-Yes state
+            // (currently only one such possibility)
+
+            if ( aGroupProps.IsPrivateAllowed() != common->IsPrivateAllowed() )
+                {
+                CHAT_DP_FUNC_DP( "HandleGroupPropertiesL",
+                                 "Private messaging turn off" );
+
+                HBufC* privatMessaging = StringLoader::LoadLC(
+                                             R_SYSTEM_MESSAGE_PRIVAT_OFF );
+
+                MCAMessage* privateChgMsg =
+                    iMessageUtils.MessageCreator().CreateSystemMessageL(
+                        MCAMessage::ESystemMessagePrivatChanged,
+                        *privatMessaging );
+
+                // Append message
+                CAMessageUtil::AppendMessageWithDateStampL(
+                    *privateChgMsg,
+                    groupChat,
+                    iMessageUtils.MessageCreator() );
+
+                CleanupStack::PopAndDestroy( privatMessaging );
+                }
+            }
+        }
+
+    CHAT_DP_FUNC_DP( "HandleGroupPropertiesL", "Passing on to wrapper" );
+    wrapper->HandleGroupPropertiesL( aOpId, groupId, aGroupProps,
+                                     aOwnProps, aCspId );
+
+    CHAT_DP_FUNC_DONE( "HandleGroupPropertiesL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleRejectListL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleRejectListL(
+    TInt aOpId,
+    const MDesCArray& aUserList,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleRejectListL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleRejectListL - opid=%d" ),
+             aOpId );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aOpId );
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleRejectListL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CHAT_DP_FUNC_DP( "HandleRejectListL", "Passing on to wrapper" );
+    wrapper->HandleRejectListL( aOpId, aUserList, aCspId );
+
+    CHAT_DP_FUNC_DONE( "HandleRejectListL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleSubscriptionL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleSubscriptionL(
+    TInt aOpId,
+    TBool aIsSubscribed,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleSubscriptionL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleSubscriptionL - opid=%d" ),
+             aOpId );
+
+    // can't be a push message
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aOpId );
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleSubscriptionL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CHAT_DP_FUNC_DP( "HandleSubscriptionL", "Passing on to wrapper" );
+    wrapper->HandleSubscriptionL( aOpId, aIsSubscribed, aCspId );
+
+    CHAT_DP_FUNC_DONE( "HandleSubscriptionL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleNewUsersL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleNewUsersL(
+    const TDesC& aGroupId,
+    const MDesCArray& aUserList,
+    const MDesCArray& aScreenNames,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleNewUsersL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleNewUsersL " ) );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleNewUsersL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    // print the user and screen names list
+#ifdef _DEBUG
+    TInt i( 0 );
+    TInt count( aUserList.MdcaCount() );
+    CHAT_DP_FUNC_DP( "HandleNewUsersL", "User list:" );
+    for ( i = 0; i < count; i++ )
+        {
+        TPtrC p = TPtrC( aUserList.MdcaPoint( i ) );
+        CHAT_DP( D_CHAT_LIT( "   %S" ), &p );
+        }
+
+    count = aScreenNames.MdcaCount();
+    CHAT_DP_FUNC_DP( "HandleNewUsersL", "Screen names list:" );
+    for ( i = 0; i < count; i++ )
+        {
+        TPtrC p = TPtrC( aScreenNames.MdcaPoint( i ) );
+        CHAT_DP( D_CHAT_LIT( "   %S" ), &p );
+        }
+#endif
+
+    // get the list of participants
+    MCAStoredGroup& group = wrapper->Group();
+
+    // this granularity is as good as any
+    CDesCArrayFlat* participants =
+        new ( ELeave ) CDesCArrayFlat( KJoinedMembersArrayGranularity );
+    CleanupStack::PushL( participants );
+    group.GetParticipantsL( *participants );
+
+    TInt numNames( aScreenNames.MdcaCount() );
+
+    // list has names
+    for ( TInt index = 0; index < numNames; index++ )
+        {
+        // don't show joined-message for persons already joined to the group
+        TInt posIgnored = 0;
+        if ( participants->Find( aScreenNames.MdcaPoint( index ),
+                                 posIgnored, ECmpFolded ) )
+            {
+            // a new user, show the message
+            HBufC* newUser = aScreenNames.MdcaPoint( index ).AllocLC();
+
+            HBufC* finalNewUserText =
+                StringLoader::LoadLC( R_SYSTEM_MESSAGE_JOIN,
+                                      *newUser );
+
+            CHAT_DP_FUNC_DP( "HandleNewUsersL",
+                             "Initiating new users message" );
+
+            MCAMessagesWriteInterface& groupChat =
+                iChatInterface.MessageWriteInterfaceL( KNullDesC,
+                                                       KNullDesC, aGroupId );
+
+            MCAMessage* newUserMsg =
+                iMessageUtils.MessageCreator().CreateSystemMessageL(
+                    MCAMessage::ESystemMessageNewUsers,
+                    *finalNewUserText );
+
+            // Append message
+            CAMessageUtil::AppendMessageWithDateStampL(
+                *newUserMsg,
+                groupChat,
+                iMessageUtils.MessageCreator() );
+
+            // finalNewUserText, newUser
+            CleanupStack::PopAndDestroy( 2, newUser );
+            }
+        }
+
+    CleanupStack::PopAndDestroy( participants );
+
+    CHAT_DP_FUNC_DP( "HandleNewUsersL", "Passing on to wrapper" );
+    wrapper->HandleNewUsersL( aGroupId, aUserList, aScreenNames, aCspId );
+
+    CHAT_DP_FUNC_DONE( "HandleNewUsersL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleLeftUsersL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleLeftUsersL(
+    const TDesC& aGroupId,
+    const MDesCArray& aUserList,
+    const MDesCArray& aScreenNames,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleLeftUsersL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleLeftUsersL" ) );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleLeftUsersL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    CHAT_DP_FUNC_DP( "HandleLeftUsersL", "Passing on to wrapper" );
+    wrapper->HandleLeftUsersL( aGroupId, aUserList, aScreenNames, aCspId );
+
+    TInt numNames( aScreenNames.MdcaCount() );
+    // list has names
+    for ( TInt index = 0; index < numNames; index++ )
+        {
+        HBufC* leftUser = aScreenNames.MdcaPoint( index ).AllocLC();
+
+        HBufC* finalLeftUserText =
+            StringLoader::LoadLC( R_SYSTEM_MESSAGE_LEAVE,
+                                  *leftUser );
+
+        CHAT_DP_FUNC_DP( "HandleLeftUsersL", "Initiating left users message" );
+
+        MCAMessagesWriteInterface& groupChat =
+            iChatInterface.MessageWriteInterfaceL( KNullDesC,
+                                                   KNullDesC, aGroupId );
+
+        MCAMessage* userLeftMsg =
+            iMessageUtils.MessageCreator().CreateSystemMessageL(
+                MCAMessage::ESystemMessageLeftUsers, *finalLeftUserText );
+
+        // Append message
+        CAMessageUtil::AppendMessageWithDateStampL(
+            *userLeftMsg,
+            groupChat,
+            iMessageUtils.MessageCreator() );
+
+        CleanupStack::PopAndDestroy( 2, leftUser ); // finalLeftUserText, leftUser
+        }
+
+    CHAT_DP_FUNC_DONE( "HandleLeftUsersL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::HandleLeaveL()
+// ---------------------------------------------------------
+//
+void CCAGroupManager::HandleLeaveL(
+    TInt aOpId,
+    const TDesC& aGroupId,
+    const TDesC& aDescription,
+    TImpsCspIdentifier& aCspId )
+    {
+    CHAT_DP_FUNC_ENTER( "HandleLeaveL" );
+    CHAT_DP( D_CHAT_LIT( "CCAGroupManager::HandleLeaveL - opid=%d" ), aOpId );
+
+    CCAGroupWrapper* wrapper = NULL;
+    if ( aOpId != 0 )
+        {
+        // operation id is valid
+        wrapper = FindGroupWrapper( aOpId );
+        }
+    else
+        {
+        // group id is valid
+        wrapper = FindGroupWrapper( aGroupId );
+        }
+
+    if ( ! wrapper )
+        {
+        CHAT_DP_FUNC_DP( "HandleLeaveL", "No wrapper, leaving" );
+        User::Leave( KErrNotFound );
+        }
+
+    // handle the leave in wrapper
+    MCAExtendedStoredGroup& gr =
+        static_cast<MCAExtendedStoredGroup&>( wrapper->Group() );
+
+    // Work with copy of group id, as wrapper gets deleted soon.
+    // Also, group id is not valid in push.
+    HBufC* grId = gr.GroupId().AllocLC();
+
+    CHAT_DP_FUNC_DP( "HandleLeaveL", "Passing on to wrapper" );
+
+    wrapper->LastImpsError( iLastImpsError );
+    //reset/consume last error
+    iLastImpsError = KErrNone;
+
+    wrapper->HandleLeaveL( aOpId, *grId, aDescription, aCspId );
+
+    if ( aOpId == 0 )
+        {
+        CHAT_DP_FUNC_DP( "HandleLeaveL",
+                         "Push message, cleaning up in HandleLeaveL" );
+        // push messages we cleanup here,
+        // pull messages in the LeaveGroupL which originated
+        // this call chain
+        CleanupLeaveGroupL( *grId );
+        }
+
+    CleanupStack::PopAndDestroy( grId );
+    CHAT_DP_FUNC_DONE( "HandleLeaveL" );
+    }
+
+// set group as un-joined, and delete wrapper, group and associated
+// group chat data
+void CCAGroupManager::CleanupLeaveGroupL( const TDesC& aGroupId )
+    {
+    CHAT_DP_FUNC_ENTER( "CleanupLeaveGroupL" );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+    if ( !wrapper )
+        {
+        CHAT_DP_FUNC_DP( "CleanupLeaveGroupL", "no wrapper, nothing to do!" );
+        return;
+        }
+    MCAExtendedStoredGroup& gr =
+        static_cast<MCAExtendedStoredGroup&>( wrapper->Group() );
+
+    CHAT_DP_FUNC_DP( "CleanupLeaveGroupL", "Leaving group" );
+
+    if ( gr.IsJoined() )
+        {
+        gr.SetJoined( EFalse );
+        iNumJoinedGroups--;
+        }
+
+    if ( ( !gr.IsOwnGroup() ) &&
+         ( gr.StorageType() != TStorageManagerGlobals::EStoragePersistent ) )
+        {
+        // not own, not stored and not joined (after CleanupLeaveGroupL)
+        // aWrappers shouldn't be on the list, so delete also from storage
+        // (not from server)
+        CHAT_DP_FUNC_DP( "CleanupLeaveGroupL", "Deleting aWrapper and group" );
+        // delete returns error code of network delete operation
+        // in case of group leave it is not import if group deleting fails
+        // from server, return code ignored
+        DeleteGroupL( aGroupId, EFalse );
+        }
+    else
+        {
+        // delete all server-related things:
+        // members, participants, admin status, whispering, etc.
+        gr.ResetMembersLocally();
+        gr.ResetParticipantsL();
+
+        gr.SetAdmin( EFalse );
+
+        // delete wrapper
+        DeleteGroupWrapper( aGroupId );
+        RemoveChatDataL( aGroupId );
+        }
+
+    CHAT_DP_FUNC_DONE( "CleanupLeaveGroupL" );
+    }
+
+
+
+// ---------------------------------------------------------
+// CCAGroupManager::NumberOfCreatedGroupsDuringSession
+// ---------------------------------------------------------
+//
+TInt CCAGroupManager::NumberOfCreatedGroupsDuringSession() const
+    {
+    return iNumCreatedGroups;
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::GroupOperations
+// ---------------------------------------------------------
+//
+MCAGroupOperations* CCAGroupManager::GroupOperationsL( const TDesC& aId )
+    {
+    CHAT_DP_FUNC_ENTER( "GroupOperationsL" );
+
+    CCAGroupWrapper* groupWrapper = FindGroupWrapper( aId );
+    if ( groupWrapper )
+        {
+        // found an existing wrapper
+        CHAT_DP_FUNC_DONE( "GroupOperationsL" );
+        return groupWrapper;
+        }
+
+    CHAT_DP_FUNC_DONE( "GroupOperationsL" );
+
+    // no existing wrapper. make a new one, store it and return it
+    return CreateWrapperL( aId );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::SetGroupEventObserver
+// Call with NULL observer is safely handled at all times
+// ---------------------------------------------------------
+//
+void CCAGroupManager::SetGroupEventObserverL( const TDesC& aId,
+                                              MCAGroupEventObserver* aObserver )
+    {
+    CHAT_DP_FUNC_ENTER( "SetGroupEventObserverL" );
+
+    CCAGroupWrapper* groupWrapper = FindGroupWrapper( aId );
+
+    if ( ! groupWrapper )
+        {
+        // not found
+        if ( aObserver )
+            {
+            // valid observer
+            CHAT_DP_FUNC_DP( "SetGroupEventObserverL", "Creating new wrapper" );
+            groupWrapper = CreateWrapperL( aId );
+            }
+        }
+
+    if ( groupWrapper )
+        {
+        // no new wrappers are to be created for null observers - in
+        // such a case there's no observer anyway!
+        CHAT_DP_FUNC_DP( "SetGroupEventObserverL", "Setting event observer" );
+        groupWrapper->RegisterEventObserver( aObserver );
+        }
+
+    CHAT_DP_FUNC_DONE( "SetGroupEventObserverL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::CreateGroupL
+// ---------------------------------------------------------
+//
+HBufC* CCAGroupManager::CreateGroupL(
+    CImpsCommonGroupProps* aProperties,
+    CImpsPrivateGroupProps* aPrivProperties,
+    const TDesC& aScreenName,
+    TBool aJoinGroup,
+    TBool aIsWhisperingEnabled,
+    TInt& aErrorCode
+)
+    {
+    CHAT_DP_FUNC_ENTER( "CreateGroupL" );
+
+    if ( !aProperties || !aPrivProperties )
+        {
+        User::Leave( KErrArgument );
+        }
+    // 1. create group to storage
+    // 2. invent a group name for the newly created group
+    // 3. create a group wrapper
+    // 4. pass this method on to the group wrapper
+    // 5. return the group id
+
+
+    // deleting old values, just in case that previous CreateGroup has failed
+    delete iProperties;
+    delete iPrivProperties;
+    iProperties = aProperties;
+    iPrivProperties = aPrivProperties;
+
+    // create group
+    MCAExtendedStoredGroup* exGrp = iStoredGroups->CreateGroupL();
+    // Check memory level, leaves group creation with KErrDiskFull
+    // now if new group cannot be added to persistent database
+    TRAPD( err, iStoredGroups->CheckFreespaceLevelL( exGrp ) );
+    if ( err == KErrDiskFull )
+        {
+        // Make sure nothing is left to storage
+        iStoredGroups->DeleteGroupL( exGrp );
+        User::Leave( err );
+        }
+    exGrp->SetAdmin( ETrue );
+    exGrp->SetGroupNameL( aProperties->GroupName() );
+
+    CHAT_DP( D_CHAT_LIT( "Group whisper, common=%d, priv=%d" ),
+             aProperties->IsPrivateAllowed(),
+             aPrivProperties->IsPrivateAllowed() );
+
+    TBool allDone = EFalse;
+    TInt maxTries( KMaxGroupIds );   // fail-safe to prevent infinite loop
+
+    CCAGroupWrapper* grOp = NULL;
+    while ( ( ! allDone ) && ( maxTries > 0 ) )
+        {
+        HBufC* groupId = NULL;
+        while ( !groupId )
+            {
+            groupId = GenerateGroupIdLC( aProperties->GroupName() );
+            if ( iStoredGroups->FindGroup( *groupId ) )
+                {
+                // group was found from storage, so let's
+                // delete it and try next one
+                CleanupStack::PopAndDestroy( groupId );
+                groupId = NULL;
+                }
+            }
+
+        // now we have a unique group id that is not in storage,
+        exGrp->SetGroupIdL( *groupId );
+        CleanupStack::PopAndDestroy( groupId );
+        groupId = NULL;
+
+        if ( !grOp )
+            {
+            // we don't have a wrapper yet, so let's
+            // create it for the group and set the properties
+            grOp = static_cast<CCAGroupWrapper*>( GroupOperationsL(
+                                                      exGrp->GroupId() ) );
+            // wrapper takes the ownership of properties from now on
+            grOp->SetLocalProperties( aProperties, aPrivProperties );
+            iProperties = NULL;
+            iPrivProperties = NULL;
+            }
+
+
+        // finalize group creation to the network
+        CHAT_DP_FUNC_DP( "CreateGroupL", "Creating group to network" );
+        aErrorCode = grOp->CreateGroupL( aScreenName, aJoinGroup );
+
+        if ( ( aErrorCode == KErrNone )
+             || ( aErrorCode == ECSPPartiallySuccessful )  )
+            {
+            // search groups from server, in order to get the new groupid
+            // begin
+            HBufC* loggedUserId = iSettingsAPI.ValueL( MCASettings::EOwnWVUserID );
+            CleanupStack::PushL( loggedUserId );
+            
+            CSearchPairs* tempPairs = 
+				new ( ELeave ) CSearchPairs( KSearchPairsSmallGranularity );
+            CleanupStack::PushL( tempPairs );
+            CImpsSearchRequest* tempRequest = CImpsSearchRequest::NewL();
+            CleanupStack::PushL( tempRequest );
+            tempRequest->SetRequestL( EImpsGroupUserIDOwner, *loggedUserId );
+            tempPairs->AppendL( tempRequest );
+
+            // If there is error in search, return so we don't send stop error request
+            TInt searchError( KErrNone );
+            TInt err( KErrNone );
+            TRAP( err, searchError = 
+				iSearchAPI.StartSearchL( *tempPairs, KMaxGroupSearchLimit, NULL ) );
+                
+            CleanupStack::PopAndDestroy( 3, loggedUserId );// tempRequest, tempPairs, loggedUserId
+            
+            if ( err != KErrNone )
+            	{
+            	User::LeaveIfError( err );
+            	}
+            else if ( searchError != KErrNone )
+            	{
+            	User::LeaveIfError( searchError );
+            	}
+            
+            TInt resultCount( 
+            		iSearchAPI.SearchDataInterface()->SearchDataCount() );
+
+            for ( TInt counter( 0 ); counter < resultCount; counter++ )
+            	{
+            	// see if this group is already in storage
+            	TPtrC groupId( iSearchAPI.SearchDataInterface()->SearchData( 
+            			counter ) );
+            	MCAStoredGroup* group = iStoredGroups->FindGroup( groupId );
+
+            	if ( !group )
+            		{
+            		exGrp->SetGroupIdL( groupId );
+            		break;
+            		}
+            	}
+            // end
+
+            // must update to the server
+            if ( aIsWhisperingEnabled )
+                {
+                SetPrivateAllowedL( *grOp, *aPrivProperties );
+                }
+
+            // this is our group
+            exGrp->SetOwnGroup( ETrue );
+
+            if ( !exGrp->IsJoined() && aJoinGroup )
+                {
+                iNumJoinedGroups++;
+                }
+            // set the joined status
+            exGrp->SetJoined( aJoinGroup );
+
+            // make it a favorite, automatically
+            exGrp->SaveChangesL();
+
+            // tell the UI the group has been added
+            iStoredGroups->SignalGroupAddedL( exGrp->GroupId() );
+
+            aErrorCode = KErrNone; // it's fully successful now...
+            allDone = ETrue;       // get out too
+            }
+        else
+            {
+            if ( aErrorCode == ECSPGroupAlreadyExists )
+                {
+                // group already exists
+                CHAT_DP_FUNC_DP( "CreateGroupL",
+                                 "Wrapper's CreateGroupL failed, trying with another groupid" );
+
+                maxTries--;
+                }
+            else
+                {
+                // delete the group from storage if CreateGroupL failed,
+                // aErrorCode is already set for use after return
+                CHAT_DP_FUNC_DP( "CreateGroupL",
+                                 "Wrapper's CreateGroupL failed, aborting" );
+                // delete return value is network operation error code
+                // we are not interested it,
+                // because group creation already failed
+                DeleteGroupL( exGrp->GroupId(), EFalse );
+                // group got deleted, return NULL
+                return NULL;
+                }
+            }
+        }
+
+    // failsafe, if we reached the maximum number of tries
+    if ( maxTries == 0 )
+        {
+        // everything is cleaned up in the loop
+        return NULL;
+        }
+
+    // Add creator of group as participant to group.
+    // Just because creator is participating group.
+    // Server does not always inform us for this one.
+    CDesCArrayFlat* participants =
+        new ( ELeave ) CDesCArrayFlat( KJoinedMembersArrayGranularity );
+    CleanupStack::PushL( participants );
+    participants->AppendL( aScreenName );
+    exGrp->AddParticipantL( *participants );
+    CleanupStack::PopAndDestroy( participants );
+
+    iNumCreatedGroups++;
+
+    CHAT_DP_FUNC_DONE( "CreateGroupL" );
+
+    return exGrp->GroupId().AllocL(); // ownership is transferred
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::JoinGroupL
+// We must wrap JoinL to make sure the properties are
+// in place. It can't be done in wrapper as it will
+// mess up the message sequences.
+// ---------------------------------------------------------
+//
+TInt CCAGroupManager::JoinGroupL( const TDesC& aGroupId,
+                                  const TDesC& aScreenName, TBool aUsers,
+                                  TBool aIsFavourite, TBool aIsWhisperingEnabled )
+    {
+    CHAT_DP_FUNC_ENTER( "JoinGroupL" );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+
+    if ( ! wrapper )
+        {
+        // no wrapper, create one
+        // exists in a internal list, so no need to use cleanupstack
+        wrapper = CreateWrapperL( aGroupId );
+        }
+
+    // check if we need to fetch the properties from the network
+    CImpsCommonGroupProps* commonProps = NULL;
+    CImpsPrivateGroupProps* privateProps = NULL;
+    wrapper->LocalProperties( commonProps, privateProps );
+    // ownership is kept in wrapper
+
+    // now we can join the group
+    TInt joinStatus = KErrNone;
+    joinStatus = wrapper->JoinL( aScreenName, aUsers, aIsFavourite );
+    if ( joinStatus == EOperationCancelled)
+        {
+        return joinStatus;    
+        }
+    TInt retStatus( KErrNone );
+    if ( ( !commonProps ) || ( !privateProps ) )
+        {
+        // no properties, so request from network
+        // Store op id of properties request
+        iPropsOpIdWhileJoining = wrapper->OperationId() + 1;
+        retStatus = wrapper->GetPropertiesL( commonProps, privateProps );
+        if ( retStatus == EOperationCancelled)
+            {
+            return retStatus;
+            }
+        if ( retStatus != ECSPInsufficientGroupPrivileges )
+            {
+            if ( ( retStatus != ECSPSuccessful ) && ( retStatus != KErrNone ) )
+                {
+                // got some failure
+                iPropsOpIdWhileJoining = KErrNotFound;  // Init to -1
+                return retStatus;
+                }
+
+            // fail-safe for buggy server returning NULL to these for some reason
+            if ( ( ! commonProps ) || ( ! privateProps ) )
+                {
+                iPropsOpIdWhileJoining = KErrNotFound;  // Init to -1
+                return ECSPInsufficientGroupPrivileges;
+                }
+            }
+        }
+
+    // Note! ChatData is created in HandleJoinL, but HandleJoinL
+    // is not called if we get 807 ECSPGroupAlreadyJoined. So we must work
+    // around to recover from that.
+
+    if ( joinStatus == ECSPGroupAlreadyJoined )
+        {
+        // Make sure our status is in sync with server
+        // status, then we can recover from 807 ECSPGroupAlreadyJoined.
+        // If we get ECSPGroupAlreadyJoined, HandleJoinL is never called,
+        // but HandleErrorL (Engine) instead!
+
+        // create the chat data, if we don't have it
+
+        MCAMessagesWriteInterface& groupChat =
+            iChatInterface.MessageWriteInterfaceL(
+                KNullDesC, KNullDesC, aGroupId,
+                MCAMessagesReadInterface::EGroupContainer );
+        groupChat.SetScreenNameL( aScreenName );
+
+        // make sure it's marked as joined in the UI
+        MCAExtendedStoredGroup& grp =
+            static_cast<MCAExtendedStoredGroup&>( wrapper->Group() );
+        if ( !grp.IsJoined() )
+            {
+            grp.SetJoined( ETrue );
+            iNumJoinedGroups++;
+            }
+        }
+
+    // also update private properties to the group
+    if ( ( joinStatus == ECSPGroupAlreadyJoined )
+         || ( joinStatus == ECSPSuccessful )
+         || ( joinStatus == KErrNone ) )
+        {
+        if ( aIsWhisperingEnabled && privateProps )
+            {
+            joinStatus = SetPrivateAllowedL( *wrapper, *privateProps );
+            if ( joinStatus == EOperationCancelled )
+            	{
+            	return joinStatus;
+            	}
+            }
+        joinStatus = KErrNone;
+        }
+
+    CHAT_DP_FUNC_DONE( "JoinGroupL" );
+
+    return joinStatus;
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::LeaveJoinedGroups
+// ---------------------------------------------------------
+//
+void CCAGroupManager::LeaveJoinedGroupsL()
+    {
+    TInt count = iGroupWrappers.Count();
+    for ( TInt i = count - 1; i >= 0; --i )
+        {
+        MCAStoredGroup& group = iGroupWrappers[ i ]->Group();
+        if ( group.IsJoined() )
+            {
+            iGroupWrappers[ i ]->LeaveL();
+            CleanupLeaveGroupL( group.GroupId() );
+            }
+        }
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::LeaveGroupL
+// We must wrap LeaveL to make sure the UI and
+// IMPS engine running in different threads do not
+// cause race conditions when UI calls LeaveL directly
+// and IMPS engine informs via HandleLeaveL.
+// ---------------------------------------------------------
+//
+TInt CCAGroupManager::LeaveGroupL( const TDesC& aGroupId )
+    {
+    CHAT_DP_FUNC_ENTER( "LeaveGroupL" );
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+
+    if ( ! wrapper )
+        {
+        // not Leaving with KErrNone due to popular request
+        return KErrNone;
+        }
+
+    // leave the group
+    TInt retStatus = wrapper->LeaveL();
+
+    // nuke the wrappers etc.
+    CleanupLeaveGroupL( aGroupId );
+
+    CHAT_DP_FUNC_DONE( "LeaveGroupL" );
+    return retStatus;
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::GenerateGroupIdLC
+// XXX cleaned up the forbidden char removal part,
+// but can this be cleaned up any more?
+// ---------------------------------------------------------
+//
+HBufC* CCAGroupManager::GenerateGroupIdLC( const TDesC& aResourcePart )
+    {
+
+    HBufC* groupId = HBufC::NewLC( KMaxGroupIDLength );
+    TPtr groupIdPtr( groupId->Des() );
+
+    // Retrieve logged user information
+    HBufC* loggedUserId = iSettingsAPI.ValueL( MCASettings::EOwnWVUserID );
+    CleanupStack::PushL( loggedUserId );
+
+    // find "@"
+    TInt atIndex( loggedUserId->FindC( KAt ) );
+    if ( atIndex == KErrNotFound )
+        {
+        atIndex = loggedUserId->Length();
+        }
+
+    // Find string between ":" and "@"
+    // If there is no "wv:" start from beginning of user id
+    TInt startPosition = 0;
+    if ( KWV().CompareC( loggedUserId->Left( KWV().Length() ),
+                         KCollationLevel, NULL ) == 0 )
+        {
+        startPosition = KWV().Length();
+        }
+
+    TPtrC userBaseName( loggedUserId->Mid(
+                            startPosition, atIndex - startPosition ) );
+
+    // We must get rid of the unneeded "@" characters
+    TPtrC groupBaseName;
+    HBufC* temp = CAUtils::GenerateIdLC( aResourcePart );
+    TPtr tempPtr( temp->Des() );
+
+    // group name empty? If it is, use userBaseName after "/" in group id
+    if ( aResourcePart.Length() < 1 )
+        {
+        groupBaseName.Set( userBaseName );
+        }
+    else
+        {
+        // Sanity check. We removed characters, is there anything in the string?
+        if ( tempPtr.Length() < 1 )
+            {
+            groupBaseName.Set( userBaseName );
+            }
+        else
+            {
+            groupBaseName.Set( tempPtr );
+            }
+
+        CHAT_DP( D_CHAT_LIT( "Group base name is %S" ), &groupBaseName );
+        }
+
+    // Add "wv:" at the beginning
+    groupIdPtr.Copy( KWV );
+
+    // Add "middle" part of WV user ID
+    //
+    // EXAMPLE 1:
+    // User id = wv:jari20@domain.com
+    // Group name = grouppi
+    // Group id becomes wv:jari20/grouppi
+    //
+    // EXAMPLE 2:
+    // User id = wv:jari20@domain.com
+    // Group name is empty
+    // Group id becomes wv:jari20/jari20
+
+    groupIdPtr += userBaseName;
+
+    // Add separator "/"
+    groupIdPtr += KResourceSeparator;
+
+    TInt groupInsertIndex( groupIdPtr.Length() );
+
+    // don't add ordinal the first time
+    if ( iGroupIdOrdinal > 0 )
+        {
+        // Format ordinal, 5 is enough
+        TBuf<5> ordinal;
+        ordinal.Num( iGroupIdOrdinal );
+        // Add ordinal
+        groupIdPtr += ordinal;
+        }
+
+    // Add domain part
+    groupIdPtr += loggedUserId->Mid( atIndex );
+
+    // Add groupId last so we can know how much it can be inserted.
+    groupIdPtr.Insert( groupInsertIndex,
+                       groupBaseName.Left(
+                           KMaxGroupIDLength - groupIdPtr.Length() ) );
+
+    CleanupStack::PopAndDestroy( temp );
+    CleanupStack::PopAndDestroy( loggedUserId );
+
+    // Increase ordinal
+    ++iGroupIdOrdinal;
+
+    return groupId;
+    }
+
+
+// ---------------------------------------------------------
+// CCAGroupManager::DeleteGroupL
+// ---------------------------------------------------------
+//
+TInt CCAGroupManager::DeleteGroupL( const TDesC& aGroupId,
+                                    TBool aDeleteFromNetwork )
+    {
+    CHAT_DP_FUNC_ENTER( "DeleteGroupL" );
+
+    TBuf<KMaxGroupNameLength> groupid;
+    groupid.Copy( aGroupId.Left( groupid.MaxLength() ) );
+
+    TInt retVal( KErrNone );
+    CCAGroupWrapper* wrapper = NULL;
+
+    if ( aDeleteFromNetwork )
+        {
+        // delete from network
+
+        wrapper = FindGroupWrapper( aGroupId );
+
+        if ( ! wrapper )
+            {
+            // no wrapper, create one
+            wrapper = CreateWrapperL( aGroupId );   // CSI: 35 # Ownership is not transferred to caller.
+            }
+
+        retVal = wrapper->DeleteFromNetworkL();
+        }
+    else
+        {
+        // deleting locally, see if we have a wrapper
+        wrapper = FindGroupWrapper( aGroupId );
+        }
+
+    if ( wrapper )
+        {
+        // have wrapper, delete it
+        DeleteGroupWrapper( aGroupId );
+        }
+
+    // delete from storage manager
+    CHAT_DP_FUNC_DP( "DeleteGroupL", "Deleting from storage" );
+
+    MCAStoredGroup* group = iStoredGroups->FindGroup( aGroupId );
+
+    if ( retVal == KErrNone || retVal == ECSPGroupDoesNotExist )
+        {
+        if ( group )
+            {
+            iStoredGroups->DeleteGroupL( group );
+            }
+        }
+
+    // delete the chat data
+    RemoveChatDataL( groupid );
+    CHAT_DP_FUNC_DONE( "DeleteGroupL" );
+
+    return retVal;
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::IsAllowedAccessL
+// check access to given group
+// ---------------------------------------------------------
+//
+TBool CCAGroupManager::IsAllowedAccessL( const TDesC& aGroupId,
+                                         const CDesCArray& aCheckUsers,
+                                         CDesCArray& aNotInList )
+    {
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+    if ( !wrapper )
+        {
+        // This should never happen, but just in case...
+        CHAT_DP_TXT( "CCAGroupManager::IsAllowedAccessL - \
+                      wrapper doesn't exist" );
+        return EFalse;
+        }
+    MCAStoredGroup& group = wrapper->Group();
+
+    aNotInList.Reset();
+
+    // all users, including administrators
+    CDesCArray* allGroupUsers =
+        new ( ELeave ) CDesCArrayFlat( KJoinedMembersArrayGranularity );
+    CleanupStack::PushL( allGroupUsers );
+    group.GetMembersL( *allGroupUsers );
+
+    TInt checkCount( aCheckUsers.Count() );
+    TInt position( 0 );
+
+    if ( ! IsGroupOpenL( aGroupId ) )
+        {
+        // closed group, so check access
+
+        CHAT_DP_TXT( "IsAllowedAccessL, checking for valid access" );
+        // Go through list of users to check and see if
+        // they're in the list of all users
+        for ( TInt i( 0 ); i < checkCount; ++i )
+            {
+            if ( allGroupUsers->Find( aCheckUsers[i], position, ECmpCollated ) )
+                {
+                aNotInList.AppendL( aCheckUsers[i] );
+                }
+            }
+        }
+
+    CImpsCommonGroupProps* commonProps = NULL;
+    CImpsPrivateGroupProps* privProps = NULL;
+    GetPropertiesL( aGroupId, commonProps, privProps );
+
+    TBool isLoggedUserAdmin = group.IsAdmin();
+
+    CleanupStack::PopAndDestroy( allGroupUsers );
+
+    return isLoggedUserAdmin;
+    }
+
+// check if group is open or closed
+TBool CCAGroupManager::IsGroupOpenL( const TDesC& aGroupId )
+    {
+    CImpsCommonGroupProps* commonProps = NULL;
+    CImpsPrivateGroupProps* privProps = NULL;
+    GetPropertiesL( aGroupId, commonProps, privProps );
+
+    return commonProps->IsOpen();
+    }
+
+// map group id to group name
+HBufC* CCAGroupManager::GroupNameL( const TDesC& aGroupId )
+    {
+    CImpsCommonGroupProps* commonProps = NULL;
+    CImpsPrivateGroupProps* privProps = NULL;
+    GetPropertiesL( aGroupId, commonProps, privProps );
+
+    HBufC* buf = NULL;
+    if ( commonProps )
+        {
+        buf = commonProps->GroupName().AllocL();    // CSI: 35 # No leaving code after allocation, ownership transferred to caller.
+        }
+    else
+        {
+        // can't get group name from properties
+        // returning empty groupname
+        buf = KNullDesC().AllocL();
+        }
+
+    return buf;
+    }
+
+// handles logout event
+void CCAGroupManager::HandleNetworkStateChangeL( TNetworkState  aState )
+    {
+    if ( aState == ELoggedOut )
+        {
+        RPointerArray<MCAStoredGroup> groups;
+        CleanupClosePushL( groups );
+        iStoredGroups->PopulateGroupsListL( groups );
+
+        // loop through all the groups
+        TInt count( groups.Count() );
+        for ( TInt i( 0 ); i < count; i++ )
+            {
+            MCAExtendedStoredGroup* exGrp =
+                static_cast<MCAExtendedStoredGroup*>( groups[ i ] );
+            if ( exGrp->IsJoined() )
+                {
+                // the group is joined, so leave it and perform cleanup
+                exGrp->SetJoined( EFalse );
+                iNumJoinedGroups--;
+                CleanupLeaveGroupL( exGrp->GroupId() );
+                }
+            }
+        CleanupStack::PopAndDestroy(); // groups.Close()
+        }
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::LastImpsError
+// ---------------------------------------------------------
+//
+void CCAGroupManager::LastImpsError( TInt aError )
+    {
+    iLastImpsError = aError;
+    }
+
+// used internally to find the proper group wrapper
+CCAGroupWrapper* CCAGroupManager::FindGroupWrapper( TInt aOpId )
+    {
+    TInt count( iGroupWrappers.Count() );
+
+    for ( TInt i( 0 ); i < count; i++ )
+        {
+        CCAGroupWrapper* wrapper = iGroupWrappers[i];
+        if ( aOpId == wrapper->OperationId() )
+            {
+            // found it
+            return wrapper;
+            }
+        }
+
+    // didn't find it
+    return NULL;
+    }
+
+// used internally to find the proper group wrapper based on group id
+CCAGroupWrapper* CCAGroupManager::FindGroupWrapper( const TDesC& aGroupId )
+    {
+    TInt count( iGroupWrappers.Count() );
+
+    for ( TInt i( 0 ); i < count; i++ )
+        {
+        CCAGroupWrapper* wrapper = iGroupWrappers[i];
+
+        // get the group the wrapper wraps
+        MCAStoredGroup& g = wrapper->Group();
+
+        if ( 0 == CAUtils::NeutralCompare( g.GroupId(), aGroupId ) )
+            {
+            // found it
+            return wrapper;
+            }
+        }
+
+    // didn't find it
+    return NULL;
+    }
+
+// used internally to delete the proper group wrapper based on group id
+void CCAGroupManager::DeleteGroupWrapper( const TDesC& aGroupId )
+    {
+    CHAT_DP_FUNC_ENTER( "DeleteGroupWrapper" );
+
+    TInt count( iGroupWrappers.Count() );
+
+    for ( TInt i( 0 ); i < count; i++ )
+        {
+        CCAGroupWrapper* wrapper = iGroupWrappers[i];
+
+        // get the group the wrapper wraps
+        MCAStoredGroup& g = wrapper->Group();
+
+        if ( 0 == g.GroupId().CompareC( aGroupId, KCollationLevel, NULL ) )
+            {
+            CHAT_DP_FUNC_DP( "DeleteGroupWrapper", "Found wrapper, deleting it" );
+            // found it! delete it
+            iGroupWrappers.Remove( i );
+            delete wrapper;
+            iGroupWrappers.Compress();
+            return;
+            }
+        }
+
+    // didn't find it
+    CHAT_DP_TXT( "CCAGroupManager::DeleteGroupWrapper tried to delete wrapper \
+                  that doesn't exist!" );
+
+    CHAT_DP_FUNC_DONE( "DeleteGroupWrapper" );
+    }
+
+// get properties for the given group
+void CCAGroupManager::GetPropertiesL( const TDesC& aGroupId,
+                                      CImpsCommonGroupProps*& aCommonProps,
+                                      CImpsPrivateGroupProps*& aPrivProps )
+    {
+    CHAT_DP_FUNC_ENTER( "PropertiesL" );
+
+    TBool tempGroup( EFalse );
+    MCAStoredGroup* group = iStoredGroups->FindGroup( aGroupId );
+    if ( !group )
+        {
+        group = iStoredGroups->CreateGroupL();  // CSI: 35 # Ownership is not transferred to caller.
+        static_cast<MCAExtendedStoredGroup*>( group )->SetGroupIdL( aGroupId );
+        static_cast<MCAExtendedStoredGroup*>( group )->SetVisible( EFalse );
+        tempGroup = ETrue;
+        }
+
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+    if ( ! wrapper )
+        {
+        wrapper = CreateWrapperL( aGroupId );   // CSI: 35 # Ownership is not transferred to caller.
+        }
+
+    // ownership does not get transferred
+    wrapper->GetPropertiesL( aCommonProps, aPrivProps );
+
+    if ( tempGroup )
+        {
+        DeleteGroupL( aGroupId, EFalse );
+        }
+
+    CHAT_DP_FUNC_DONE( "PropertiesL" );
+    }
+
+// create a group wrapper
+CCAGroupWrapper* CCAGroupManager::CreateWrapperL( const TDesC& aGroupId )
+    {
+    CHAT_DP_FUNC_ENTER( "CreateWrapperL" );
+
+    // wrapper needs a group
+    MCAStoredGroup* group = iStoredGroups->FindGroup( aGroupId );
+    if ( !group )
+        {
+        // no such group at all
+        CHAT_DP_FUNC_DP( "CreateWrapperL", "No group!!" );
+        User::Leave( KErrNotFound );
+        }
+
+    CHAT_DP_FUNC_DP( "CreateWrapperL", "Found group" );
+
+    // create a new wrapper
+    CHAT_DP_FUNC_DP( "CreateWrapperL", "Creating group wrapper" );
+
+    CCAGroupWrapper* groupWrapper = CCAGroupWrapper::NewL( iSettingsAPI,
+                                                           static_cast<MCAExtendedStoredGroup&>( *group ), iRequestMapper,
+                                                           iImpsFactory->CreateGroupClientL() );
+    CleanupStack::PushL( groupWrapper );
+
+    // ownership transferred to array
+    TInt retVal( iGroupWrappers.Append( groupWrapper ) );
+
+    User::LeaveIfError( retVal );
+    CleanupStack::Pop( groupWrapper );
+
+    CHAT_DP_FUNC_DONE( "CreateWrapperL" );
+
+    // that's it
+    return groupWrapper;
+    }
+
+// set the private messaging to allowed
+TInt CCAGroupManager::SetPrivateAllowedL( CCAGroupWrapper& aWrapper,
+                                          CImpsPrivateGroupProps& aPrivProps )
+    {
+    aPrivProps.SetPrivateAllowed( EImpsPropYes );
+    // the return value of updateproperties can be ignored because
+    // there's no point showing "server error" note if the joining
+    // process was otherwise successful.
+    TInt operationvalue = aWrapper.UpdatePropertiesL( NULL, &aPrivProps );
+    
+    return operationvalue;
+    }
+
+// remove the given chat data
+void CCAGroupManager::RemoveChatDataL( const TDesC& aGroupId )
+    {
+    CHAT_DP_FUNC_ENTER( "RemoveChatDataL" );
+    iChatInterface.DeleteChatL( KNullDesC, KNullDesC, aGroupId );
+    CHAT_DP_FUNC_DONE( "RemoveChatDataL" );
+    }
+
+// ---------------------------------------------------------
+// CCAGroupManager::WriteWelcomeMessageL
+// ---------------------------------------------------------
+//
+void CCAGroupManager::WriteWelcomeMessageL(
+    const TDesC& aWelcomeMessage,
+    const TDesC& aGroup,
+    const TDesC& aTopic,
+    MCAMessagesWriteInterface& aMessages )
+    {
+    // I can't figure out a situation in which these would require a
+    // datestamp. But if that is the case at some point, use
+    // CAMessageUtil::AppendMessageWithDateStampL for appending.
+
+    // show customized welcome message
+    if ( aWelcomeMessage.Length() > 0 )
+        {
+        aMessages.AppendL(
+            iMessageUtils.MessageCreator().CreateSystemMessageL(
+                MCAMessage::ESystemMessageWelcome, aWelcomeMessage ) );
+        }
+    // show default welcome message
+    else
+        {
+        HBufC* welcome =
+            StringLoader::LoadLC( R_SYSTEM_MESSAGE_WELCOME_TEXT, aGroup );
+
+        aMessages.AppendL(
+            iMessageUtils.MessageCreator().CreateSystemMessageL(
+                MCAMessage::ESystemMessageWelcome, *welcome ) );
+
+        CleanupStack::PopAndDestroy( welcome );
+        }
+
+    // Only show real topic
+    if ( aTopic.Length() > 0 )
+        {
+        // load & show default topic string
+        HBufC* topicText =
+            StringLoader::LoadLC( R_SYSTEM_MESSAGE_TOPIC_TEXT, aTopic );
+        aMessages.AppendL(
+            iMessageUtils.MessageCreator().CreateSystemMessageL(
+                MCAMessage::ESystemMessageWelcome, *topicText ) );
+        CleanupStack::PopAndDestroy( topicText );
+        }
+    }
+// ---------------------------------------------------------
+// CCAGroupManager::CancelGroupOperationL
+// ---------------------------------------------------------
+//
+void CCAGroupManager::CancelGroupOperationL( const TDesC& aGroupId )
+    {
+    CCAGroupWrapper* wrapper = FindGroupWrapper( aGroupId );
+    if ( !wrapper )
+        {
+        return;
+        }
+   CCARequest* request = wrapper->Request();
+       if ( request )
+        {
+        request->StopWaitIfNeeded(); 
+        request->SetErrorCode( EOperationCancelled );   
+        }   
+    }
+//  End of File