srsf/nssvasapi/nssvascore/src/nssvascspeechitemtrainer.cpp
branchRCL_3
changeset 19 e36f3802f733
parent 0 bf1d17376201
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/srsf/nssvasapi/nssvascore/src/nssvascspeechitemtrainer.cpp	Wed Sep 01 12:29:17 2010 +0100
@@ -0,0 +1,1965 @@
+/*
+* Copyright (c) 2004-2006 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:  The CNssSpeechItemTrainer provides methods to train, save
+*               and delete CNssSpeechItem objects in large quantities.
+*
+*/
+
+
+/* Here is a state machine for training:
+*
+* Summary:
+*
+* Idle state
+*     |
+* Waiting state     <-------------------
+*     |                                | (loop back to waiting state)
+* ###################################  |
+* # IF NEEDED: Create lexicon state #  |
+* #            |                    #  |
+* # IF NEEDED: Create grammar state #---
+* #            |                    #
+* #       Training state            #
+* ###################################
+*     |
+* Idle state
+*
+*
+*
+* State: Idle state
+* Event: A client calls TrainTextL, 
+* Action:This induces a call to CNssSpeechItemTrainer::TrainTextDealyed().
+*
+*        The basic idea of _delayed_training_ is that several training calls
+*        are piled up before taking action. When a large number of names is
+*        trained at a time, the economies of scale are considerable.
+*
+*        So, TrainTextDelayed() adds the name to the to-be-trained list,
+*        and starts a _delay_timer_. When this delay timer has finished, the
+*        actual training starts.
+* Transition: Idle state -> Waiting state
+*
+*
+* State: Waiting state
+* Event: A client calls TrainTextL.
+* Action:The function adds the name to the to-be-trained list, and
+*        resets the timer
+*        (background: In contact synchronization via PC Suite, names are added
+*         every 1/2 seconds. We want to keep piling the names  as long as
+*         new names keep coming. Since we can't wait 50 seconds
+*         before training, the only reasonable solution is to reset the timer
+*         every time a new name arrives)
+* Transition: Waiting state -> Waiting state
+*
+*
+* State: Waiting state
+* Event: Timer finishes doing time.
+* Action:Check the preparations for training:
+*         * Has the grammar for the context been created?
+*         * Has the lexicon for the context been created?
+* CASE: Preconditions OK
+*       Action    : Send async AddVoiceTags call
+*       Transition: Waiting state -> Training state
+*
+* State: Training state
+* Event: Training finished
+* Action: Save the newly created Rule IDs to the speech items.
+*         Commit the database.
+*         Make the callbacks (success or failure) to the client.
+*
+* Transition: Check if new names were piled during traning.
+*         YES: Training state -> Waiting state
+*         NO : Training state -> Idle state
+*
+*/
+
+#include "srsfbldvariant.hrh"
+#include "nssvascspeechitemtrainer.h"
+#include "nssvasctrainingparameters.h"
+#include "nssvascvasdatabase.h"
+#include "nssvasmsavetagclient.h"
+#include "nssvasmdeletetagclient.h"
+
+#include "rubydebug.h"
+
+#include <badesca.h>
+
+#ifdef _DEBUG
+    // Used in UDEB only
+    _LIT( KSpeechItemTrainerPanic, "VasCSpeechItemTrainer" );
+#endif  // _DEBUG
+
+#define DEBUGPANIC User::Panic( KSpeechItemTrainerPanic, __LINE__ )
+#define REACT( a, b ) if ( a != KErrNone ) { b; }
+#define CHECK_NOT_NULL( a ) \
+    if ( !(a) ) \
+        { \
+        return MNssSpeechItem::EVasTrainFailed; \
+        }
+#define ALLOWED_STATES( a, b, c ) \
+    if ( iState != a && iState != b && iState != c ) \
+        { \
+        return MNssSpeechItem::EVasTrainFailed; \
+        } 
+
+// tags are buffered in the buffer that grows with this granularity
+const TInt KTagBufferGranularity = 100;
+
+// rule ids are buffered in the buffer that grows with this granularity
+const TInt KRuleIdBufferGranularity = 50;
+
+// tag ids are buffered in the buffer that grows with this granularity
+const TInt KRemoveTagIdBufferGranularity = 50;
+
+// Granularity for the array of words per sinde phrase
+const TInt KSindeWordArrayGranularity = 2;
+
+
+// Local function declarations
+TInt EnforceRuleIdCountInvariant( RArray<TUint32>& aArray, TInt aCount );
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::CNssSpeechItemTrainer
+// C++ default constructor can NOT contain any code, that
+// might leave.
+// ---------------------------------------------------------
+//
+CNssSpeechItemTrainer::CNssSpeechItemTrainer( CNssVASDatabase* aDatabase )
+: iDatabase( aDatabase ),
+  iSpeechItemBuffer       ( NULL ),
+  iSpeechItemTrainingBuffer( NULL ),
+  iGrammarIdBuffer        ( NULL ),
+  iGrammarIdDeletingBuffer( NULL ),
+  iTagBuffer(NULL)
+    {
+    // empty
+    }
+
+
+// -----------------------------------------------------------------------------
+// CNssSpeechItemTrainer::ConstructL
+// Symbian 2nd phase constructor can leave.
+// This is overloaded function for SpeechItemTrainer from database
+// -----------------------------------------------------------------------------
+//
+void CNssSpeechItemTrainer::ConstructL()
+    {
+    RUBY_DEBUG_BLOCK("CNssSpeechItemTrainer::ConstructL");
+    iActionTracker = CNssTrainingActionTracker::NewL( *this );
+
+    // Create buffer for grouping phrases in training
+    iSpeechItemBuffer = new(ELeave)RPointerArray<CNssSpeechItem>;
+    iSpeechItemTrainingBuffer = 0; // No name being currently trained.
+    }
+
+
+// -----------------------------------------------------------------------------
+// CNssSpeechItemTrainer::NewL
+// Two-phased constructor.
+// This is for new SpeechItemTrainer
+// -----------------------------------------------------------------------------
+//
+CNssSpeechItemTrainer* CNssSpeechItemTrainer::NewL( CNssVASDatabase* aDatabase )
+    {
+    RUBY_DEBUG_BLOCK( "CNssSpeechItemTrainer::NewL" );
+
+    CNssSpeechItemTrainer* self = NewLC( aDatabase );
+    CleanupStack::Pop( self );
+    return self;
+    }
+
+
+// -----------------------------------------------------------------------------
+// CNssSpeechItemTrainer::NewLC
+// Two-phased constructor.
+// This is overloaded function for SpeechItemTrainer from database
+// -----------------------------------------------------------------------------
+//
+CNssSpeechItemTrainer* CNssSpeechItemTrainer::NewLC( CNssVASDatabase* aDatabase )
+    {
+    CNssSpeechItemTrainer* self
+        = new (ELeave) CNssSpeechItemTrainer( aDatabase );
+
+    CleanupStack::PushL( self );
+    self->ConstructL();
+    return self;
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::~CNssSpeechItemTrainer
+// Delete the SpeechItemTrainer object and set the Portal's 
+// state to Terminate.
+// The Trainer does not delete the Portal, the Portal  
+// deletes itself.
+// ---------------------------------------------------------
+//
+CNssSpeechItemTrainer::~CNssSpeechItemTrainer()
+    {
+    RUBY_DEBUG0( "CNssSpeechItemTrainer::~CNssSpeechItemTrainer" );
+
+    delete iActionTracker;
+
+
+    // Buffer for grouping the MNssSpeechItems for efficient training.
+    if ( iSpeechItemBuffer )
+        {
+        iSpeechItemBuffer->Reset();
+        delete iSpeechItemBuffer;
+        }
+
+    // Names, which are being trained by CSISpeechRecognitionUtility.
+    if ( iSpeechItemTrainingBuffer )
+        {
+        iSpeechItemTrainingBuffer->Reset();
+        delete iSpeechItemTrainingBuffer;
+        }
+
+    // This buffer is sent to SRS Utility. Contains: The recognition phrase split into subwords.
+    iPhraseArray.ResetAndDestroy();
+
+    // Training parameters
+    delete iTrainingParams;
+
+    // Context
+    delete iContext;
+
+    // Rule ID array. Return values from AddVoiceTags() are placed here.
+    if ( iRuleIDArray )
+        {
+        iRuleIDArray->Close();
+        delete iRuleIDArray;
+        }
+      
+    if ( iDeleteRuleIDArray )
+    	{
+        RUBY_DEBUG0( "CNssSpeechItemTrainer::~CNssSpeechItemTrainer Deleting iDeleteRuleIDArray" );
+    	iDeleteRuleIDArray->Close();
+    	delete iDeleteRuleIDArray;
+    	}
+
+    // Handle to speech services
+    if ( iSrsApi )
+        {
+        iSrsApi->CancelUtility();
+        delete iSrsApi;
+        iSrsApi = NULL;
+        }
+
+    // The tags to be saved: Owned by client
+    // RPointerArray<CNssTag> *iTagBuffer;
+    if ( iTagBuffer )
+        {
+        iTagBuffer->Reset();
+        delete iTagBuffer;
+        iTagBuffer = 0;
+        }
+
+    if ( iGrammarIdBuffer )
+        {
+        iGrammarIdBuffer->Reset();
+        delete iGrammarIdBuffer;
+        iGrammarIdBuffer = 0;
+        }
+
+    if ( iGrammarIdDeletingBuffer )
+        {
+        iGrammarIdDeletingBuffer->Reset();
+        delete iGrammarIdDeletingBuffer;
+        iGrammarIdDeletingBuffer = 0;
+        }
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::RetrainTextDelayed
+// Does delayed retraining.
+// ---------------------------------------------------------
+//
+MNssSpeechItem::TNssSpeechItemResult CNssSpeechItemTrainer::RetrainTextDelayed(
+    MNssTrainTextEventHandler* aEventHandler,
+	CNssTrainingParameters*    aTrainingParams,
+    CNssSpeechItem&            aSpeechItem,
+    CNssContext&               aContext)
+    {
+    RUBY_DEBUG0( "CNssSpeechItemTrainer::RetrainTextDelayed" );
+
+    // Check parameters: 
+    // callback 
+    CHECK_NOT_NULL( aEventHandler );
+
+    // phrase to be trained
+    CHECK_NOT_NULL( aSpeechItem.Text().Length() );
+
+    // Check state
+    ALLOWED_STATES( EStateIdle, ERetrainStateWaiting, ERetrainStateRetraining );
+
+    // Check event handler
+    if ( !iTrainEventHandler )
+        {
+        iTrainEventHandler = aEventHandler;
+        }
+    else if ( iTrainEventHandler != aEventHandler )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // The training parameters and the context 
+    // must be the same for all in the buffer.
+    TRAPD( error,
+        if ( !CheckTrainingParametersL( aTrainingParams ) ||
+             !CheckContext( aContext ) )
+            {
+            return( MNssSpeechItem::EVasTrainFailed );
+            }
+         );
+    if ( error != KErrNone )
+        {
+        return MNssSpeechItem::EVasTrainFailed;
+        }
+
+    // Buffer the training request
+
+    // Allocate buffer, if not done earlier
+    if ( !SpeechItemBufferNeeded() )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // If this is the first tag, save Lexicon ID and Grammar ID.
+    if ( iSpeechItemBuffer->Count() == 0 )
+        {
+        iLexiconId = aContext.LexiconId();
+        iGrammarId = aContext.GrammarId();
+        }
+    else
+        {
+        // The tags after the first one must have the same Gramamr ID & Lexicon ID.
+        if ( iLexiconId != aContext.LexiconId() ||
+            iGrammarId != aContext.GrammarId() )
+            {
+            return( MNssSpeechItem::EVasTrainFailed );
+            }
+        }
+
+    // Add request to buffer
+    TInt ret = iSpeechItemBuffer->Append( &aSpeechItem );
+
+    if ( ret != KErrNone )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // Restart timer. When the timer has waited for a defined period, the names
+    // get retrained. Timer is reseted every time RetrainTextDelayed is called.
+    //
+    // Retraining in groups is more efficient. The delay ensures this grouping.
+    switch( iState )
+        {
+        case ERetrainStateRetraining:
+            // Wait for the retraining to finish before acting.
+            break;
+
+        case EStateIdle:
+            SetState( ERetrainStateWaiting );
+            RestartTimer();
+            break;
+
+        case ERetrainStateWaiting:
+            RestartTimer();
+            break;
+
+        default:
+            return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    return( MNssSpeechItem::EVasErrorNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SaveTagDelayed
+// Does delayed saving.
+// ---------------------------------------------------------
+//
+TInt CNssSpeechItemTrainer::SaveTagDelayed(
+    MNssSaveTagClient* aSaveTagClient,
+    CNssTag& aTag )
+    {
+    RUBY_DEBUG0( "CNssSpeechItemTrainer::SaveTagDelayed" );
+
+    // Check internal state: Must not be training
+    if ( this->iState != EStateIdle &&
+         this->iState != ESaveStateWaiting &&
+         this->iState != ESaveStateSaving )
+        {
+        return( KErrNotReady );
+        }
+
+    // Check arguments
+    if ( aSaveTagClient == 0  )
+        {
+        return KErrArgument;
+        }
+
+    if ( !((CNssSpeechItem*)aTag.SpeechItem())->Trained() )
+        {
+        return KErrNotReady;
+        }
+
+    // The event handler must be the same for all group-saved tags
+    if ( !iSaveEventHandler )
+        {
+        iSaveEventHandler = aSaveTagClient;
+        }
+    else if ( aSaveTagClient != iSaveEventHandler )
+        {
+        return KErrArgument;
+        }
+
+    // Buffer the saving request
+
+    // Allocate buffer, if not done earlier
+    if ( !iTagBuffer )
+        {
+        iTagBuffer = new RPointerArray<CNssTag>( KTagBufferGranularity );
+
+        if ( !iTagBuffer )
+            {
+            return KErrNoMemory;
+            }
+        }
+
+    // Add request to buffer
+    TInt ret = iTagBuffer->Append( &aTag );
+
+    if ( ret != KErrNone )
+        {
+        return( ret );
+        }
+
+    // Restart timer. When the timer has waited for a defined period, the names
+    // get saved. Timer is reseted every time SaveTagDelayed is called.
+    //
+    // Saving in groups is more efficient. The delay ensures this grouping.
+    switch( iState )
+        {
+        case ESaveStateSaving:
+            // Wait for the training to finish before acting.
+            break;
+
+        case EStateIdle:
+            SetState( ESaveStateWaiting );
+            RestartTimer();
+            break;
+
+        case ESaveStateWaiting:
+            RestartTimer();
+            break;
+
+        default:
+            return( KErrCorrupt );
+        }
+
+    return( KErrNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DeleteTagDelayed
+// Does delayed deleting.
+// (other items were commented in a header)
+// ---------------------------------------------------------
+//
+TInt CNssSpeechItemTrainer::DeleteTagDelayed(
+    MNssDeleteTagClient* aDeleteTagClient,
+    CNssTag& aTag )
+    {
+    RUBY_DEBUG0( "CNssSpeechItemTrainer::DeleteTagDelayed" );
+
+    // Check internal state: Must not be doing something else
+    ALLOWED_STATES( EStateIdle, EDeleteStateWaiting, EDeleteStateDeleting );
+
+    // Check arguments
+    CHECK_NOT_NULL( aDeleteTagClient );
+
+    // The event handler must be the same for all group-deleted tags
+    if ( !iDeleteEventHandler )
+        {
+        iDeleteEventHandler = aDeleteTagClient;
+        }
+    else if ( aDeleteTagClient != iDeleteEventHandler )
+        {
+        return KErrArgument;
+        }
+
+    // Buffer the deleting request
+
+    // Allocate buffer, if not done earlier
+    if ( !SpeechItemBufferNeeded() ||
+         !GrammarIdBufferNeeded()  )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // If this is the first tag, save the grammar id.
+    if ( iSpeechItemBuffer->Count() == 0 )
+        {
+        iGrammarId = ((CNssContext*)aTag.Context())->GrammarId();
+        if ( !iDeleteRuleIDArray ) 
+        	{
+        	iDeleteRuleIDArray = new RArray<TUint32>;
+        	if( !iDeleteRuleIDArray ) 
+        		{
+        		return KErrNoMemory;
+	        	}
+        	// no deletion. iDeleteRuleIDArray might still be used
+        	}
+//        delete iDeleteRuleIDArray; // if any
+//        iDeleteRuleIDArray = new RArray<TUint32>;
+        }
+    else
+        {
+        // subsequent tags must have the same grammar id.
+        if ( iGrammarId != ((CNssContext*)aTag.Context())->GrammarId() )
+            {
+            return( MNssSpeechItem::EVasTrainFailed );
+            }
+        }
+
+    // Add request to buffer
+    // iSpeechItemBuffer will be copied into iSpeechItemTrainingBuffer later and
+    // iSpeechItemTrainingBuffer will be used to delete tags from the VAS DB
+    TInt ret = iSpeechItemBuffer->Append( (CNssSpeechItem*)aTag.SpeechItem() );
+    if ( ret != KErrNone )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    RUBY_DEBUG1( "CNssSpeechItemTrainer::DeleteTagDelayed Adding [%d] to iDeleteRuleIDArray", 
+        ( ( CNssSpeechItem* )aTag.SpeechItem() )->RuleID() );
+
+    ret = iDeleteRuleIDArray->Append( ( ( CNssSpeechItem* )aTag.SpeechItem() )->RuleID() );
+
+    if ( ret != KErrNone )
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::DeleteTagDelayed Adding to iDeleteRuleIDArray failed with error [%d]", ret );
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // Restart timer. When the timer has waited for a defined period, the names
+    // get deleted. Timer is reseted every time DeleteDagDelayed is called.
+    //
+    // Deleting in groups is more efficient. The delay ensures this grouping.
+    switch( iState )
+        {
+        case EDeleteStateDeleting:
+            // Wait for the training to finish before acting.
+            break;
+
+        case EStateIdle:
+            SetState( EDeleteStateWaiting );
+            RestartTimer();
+            break;
+
+        case EDeleteStateWaiting:
+            RestartTimer();
+            break;
+
+        default:
+            return( KErrCorrupt );
+        }
+
+    return( KErrNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::TrainTextDelayed
+// Does delayed training.
+// ---------------------------------------------------------
+//
+MNssSpeechItem::TNssSpeechItemResult CNssSpeechItemTrainer::TrainTextDelayed(
+    MNssTrainTextEventHandler* aEventHandler,
+	CNssTrainingParameters*    aTrainingParams,
+    CNssSpeechItem&            aSpeechItem,
+    CNssContext&               aContext)
+    {
+    RUBY_DEBUG0( "CNssSpeechItemTrainer::TrainTextDelayed" );
+
+    // Check parameters: 
+    // callback
+    CHECK_NOT_NULL( aEventHandler );
+
+    // Text is not null
+    CHECK_NOT_NULL( aSpeechItem.Text().Length() );
+
+    // Check state
+    ALLOWED_STATES( EStateIdle, ETrainStateWaiting, ETrainStateTraining );
+
+    // Check event handler
+    if ( !iTrainEventHandler )
+        {
+        iTrainEventHandler = aEventHandler;
+        }
+    else if ( iTrainEventHandler != aEventHandler )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // The training parameters and the context 
+    // must be the same for all in the buffer.
+    TRAPD( error,
+        if ( !CheckTrainingParametersL( aTrainingParams ) ||
+             !CheckContext( aContext ) )
+            {
+            return( MNssSpeechItem::EVasTrainFailed );
+            }
+        );
+    if ( error != KErrNone )
+        {
+        return MNssSpeechItem::EVasTrainFailed;
+        }
+
+    // Buffer the training request
+    if ( !SpeechItemBufferNeeded() )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    TInt ret = iSpeechItemBuffer->Append( &aSpeechItem );
+
+    if ( ret == KErrNoMemory )
+        {
+        return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    // Restart timer. When the timer has waited for a defined period, the names
+    // get trained. Timer is reseted every time TrainTextDelayed is called.
+    //
+    // Training in groups is more efficient. The delay ensures this grouping.
+    switch( iState )
+        {
+        case ETrainStateTraining:
+            // Wait for the training to finish before acting.
+            break;
+
+        case EStateIdle:
+            SetState( ETrainStateWaiting );
+            RestartTimer();
+            break;
+
+        case ETrainStateWaiting:
+            RestartTimer();
+            break;
+
+        default:
+            return( MNssSpeechItem::EVasTrainFailed );
+        }
+
+    return( MNssSpeechItem::EVasErrorNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::HandleTrainError
+// Called if AddVoiceTags() call failed.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::HandleTrainError( RPointerArray<CNssSpeechItem>*& anItemArray )
+    {
+    RUBY_DEBUG0( "CNssSpeechItemTrainer::HandleTrainError" );
+
+    TInt count = anItemArray->Count();
+
+    CleanUpTraining();
+
+    // Announce the error to the client
+    for( TInt k=0; k<count; k++ )
+        {
+        iTrainEventHandler->HandleTrainComplete( KErrGeneral );
+        
+        }
+    iTrainEventHandler = NULL;
+    // All the errors reported, everything was rolled back
+    // Back tothe idle state
+    SetState( EStateIdle );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::CleanUpTrianing
+// Cleans memory allocated by training.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::CleanUpTraining(void)
+    {
+    // Delete buffered names
+    if ( iSpeechItemTrainingBuffer )
+        {
+        iSpeechItemTrainingBuffer->Close();
+        }
+    delete iSpeechItemTrainingBuffer;
+    iSpeechItemTrainingBuffer = 0;
+
+    // Delete parameters common to all names
+    delete iTrainingParams;
+    iTrainingParams = 0;
+
+    delete iContext;
+    iContext = 0;
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoTrainTextDelayed
+// Starts training tags. Sends the AddVoiceTags() call.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DoTrainTextDelayed()
+    {
+    SetState( ETrainStateTraining );
+
+    // Move from speech item buffers from filling state to processing state.
+
+    // names to be trained + names to be retrained
+    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
+    iSpeechItemBuffer = 0;
+
+    // Initialize iSrsApi (speech recognition utility)
+    TInt error = CreateSrsApi();
+    if ( error != KErrNone )
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::DoTrainTextDelayed Creating SrsApi failed. Error [%d]", error );
+        HandleTrainError( iSpeechItemTrainingBuffer );
+        }
+    else 
+       	{
+        	
+	    __ASSERT_DEBUG( iContext->LexiconId() != KInvalidLexiconID, 
+	                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );
+	    __ASSERT_DEBUG( iContext->GrammarId() != KInvalidGrammarID, 
+	                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );
+
+	    // Extract texts from speech items to phrase array.
+
+	    // iPhraseArray is an array of arrays: 
+	    // 
+	    //        |-- firstname1
+	    //  tag1 -|-- lastname1
+	    //
+	    //  tag2 -|-- firstname2
+	    //        |-- lastname2
+	    iPhraseArray.ResetAndDestroy();
+
+	    // Convert speech item array to the format understood by the Utility.
+	    TInt ret = SpeechItems2Phrases( *iSpeechItemTrainingBuffer, iPhraseArray );
+	    if ( ret != KErrNone )
+	        {
+	        SendTrainingCallbacks( ret );
+	        /** @todo reengineer into signle return path
+	            @todo check that everything is cleaned up before the return */
+	        return;
+	        }
+
+	    // Initialize Rule ID array. The Utility puts the IDs
+	    // of newly created rules here.
+	    // Not using (ELeave) since this is non-leaving function (trapping of (ELeave) is not allowed).
+	    iRuleIDArray = new RArray<TUint32>;
+
+	    if ( iRuleIDArray == 0 )
+	        {
+            RUBY_DEBUG0( "CNssSpeechItemTrainer::DoTrainTextDelayed iRuleIDArray == 0" );
+
+	        HandleTrainError( iSpeechItemTrainingBuffer );
+	        }
+	    else
+	       	{
+            RUBY_DEBUG1( "CNssSpeechItemTrainer::Calling AddVoiceTags (%d names)", 
+                         iPhraseArray.Count() ); 
+	       	// allocation succeeded
+
+            TRAPD( errSinde, const RArray<RTrainingLanguageArray>& languages = 
+                                        iTrainingParams->SindeLanguagesL() );
+            
+            if ( errSinde == KErrNone )
+                {
+#ifdef __SINDE_TRAINING
+                TRAP_IGNORE(
+                    const RArray<RTrainingLanguageArray>& languages = 
+                                        iTrainingParams->SindeLanguagesL();
+    		        ret = iSrsApi->AddVoiceTags( iPhraseArray,
+                                                 languages,
+                                                 (TSILexiconID)iContext->LexiconId(),
+                                                 (TSIGrammarID)iContext->GrammarId(),
+                                                 *iRuleIDArray );
+                ); // TRAP_IGNORE
+#endif // __SINDE_TRAINING
+                }
+            else
+                {
+                // Use the old style of training if SINDE languages are not available
+		        ret = iSrsApi->AddVoiceTags( iPhraseArray,
+                                             iTrainingParams->Languages(),
+                                             (TSILexiconID)iContext->LexiconId(),
+                                             (TSIGrammarID)iContext->GrammarId(),
+                                             *iRuleIDArray );
+                }
+
+		    // error -> cleanup
+		    if ( ret != KErrNone )
+		        {
+                RUBY_DEBUG1( "CNssSpeechItemTrainer::AddVoiceTags failed(%d)", ret );
+
+		        HandleTrainComplete( ret );
+		        }  // if ret is not KErrNone
+	       	}  // if iRuleIDArray allocation succeeded
+       	}  // if SRS API was created successfully
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoRetrainTextDelayed
+// Starts the 1st phase of retraining,
+// which is removing old rules.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DoRetrainTextDelayed()
+    {
+    __ASSERT_DEBUG( iSpeechItemBuffer != 0, DEBUGPANIC );
+
+    SetState( ERetrainStateRetraining );
+
+    // Move speech item buffer from filling state to processing state.
+    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
+    iSpeechItemBuffer = 0;
+
+    // Initialize iSrsApi (speech recognition utility)
+    TInt error = CreateSrsApi();
+    if ( error != KErrNone )
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::DoRetrainTextDelayed Creating SrsApi failed. Error [%d]", error );
+        HandleTrainError( iSpeechItemTrainingBuffer );
+        return;
+        }
+
+    // Start deleting tags
+    iTagDeleteCounter = 0;
+    DeleteNextTag();
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoRetrainAddVoiceTags
+// Phase 2 of retraining: Add voice tags
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DoRetrainAddVoiceTags()
+    {
+    SetState( ERetrainStateRetraining );
+
+    // Move from speech item buffers from filling state to processing state.
+
+    __ASSERT_DEBUG( iContext->LexiconId() != KInvalidLexiconID, 
+                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );
+    __ASSERT_DEBUG( iContext->GrammarId() != KInvalidGrammarID, 
+                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );
+
+    // Extract texts from speech items to phrase array.
+
+    iPhraseArray.ResetAndDestroy();
+
+    // Convert speech item array to the format understood by the Utility.
+    TInt ret = SpeechItems2Phrases( *iSpeechItemTrainingBuffer, iPhraseArray );
+    if ( ret != KErrNone )
+        {
+        SendTrainingCallbacks( ret );
+        return;
+        }
+
+    // Allocate Rule ID array. The Utility fills this array.
+    // When the utility trans a name, it attaches a Rule ID to the name.
+    iRuleIDArray = new RArray<TUint32>;
+    if ( iRuleIDArray == 0 )
+        {
+        SendTrainingCallbacks( KErrNoMemory );
+        return;
+        }
+
+    RUBY_DEBUG1( "CNssSpeechItemTrainer::Retrain Calling AddVoiceTags (%d names)", 
+                 iPhraseArray.Count() );
+
+    TRAPD( errSinde, const RArray<RTrainingLanguageArray>& languages = 
+                                        iTrainingParams->SindeLanguagesL() );
+            
+    if ( errSinde == KErrNone )
+        {
+#ifdef __SINDE_TRAINING        	
+        TRAP_IGNORE(
+            const RArray<RTrainingLanguageArray>& languages = 
+                                iTrainingParams->SindeLanguagesL();
+	        ret = iSrsApi->AddVoiceTags( iPhraseArray,
+                                         languages,
+                                         (TSILexiconID)iContext->LexiconId(),
+                                         (TSIGrammarID)iContext->GrammarId(),
+                                         *iRuleIDArray );
+        ); // TRAP_IGNORE
+#endif // __SINDE_TRAINING
+        }
+    else
+        {
+        // Use the old style of training if SINDE languages are not available
+        ret = iSrsApi->AddVoiceTags( iPhraseArray,
+                                     iTrainingParams->Languages(),
+                                     (TSILexiconID)iLexiconId,
+                                     (TSIGrammarID)iGrammarId,
+                                     *iRuleIDArray );
+        }
+
+    // error -> cleanup
+    if ( ret != KErrNone )
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::Retrain ERROR: AddVoiceTags(%d)", ret );
+
+        SendTrainingCallbacks( ret );
+        }
+
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoRetrainAddVoiceTags
+// After the new tags have been trained:
+// Announce the new rule IDs for speech items,
+// and update the rule IDs to the VAS database.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::HandleRetrainComplete( TInt aResult )
+    {
+    RUBY_DEBUG1( "CNssSpeechItemTrainer::HandleTrainComplete(%d)", aResult );
+
+    // iRuleIdArray contains the rule IDs of the newly trained tags.
+    // The following call makes sure that
+    // iRuleIdArray.Count() == iSpeechItemTrainingBuffer.Count().
+    if ( EnforceRuleIdCountInvariant( *iRuleIDArray, 
+                        iSpeechItemTrainingBuffer->Count() ) != KErrNone )
+        {
+        SendTrainingCallbacks( KErrNoMemory );
+        }
+    else
+        {
+        TInt count = iSpeechItemTrainingBuffer->Count();
+        RArray<TNssSpeechItem> ruleIdUpdateArray( KRuleIdBufferGranularity );
+
+        for( TInt itemIdx = 0; itemIdx < count; itemIdx++ )
+            {
+            CNssSpeechItem* item = (*iSpeechItemTrainingBuffer)[itemIdx];
+            TUint32    ruleID = (*iRuleIDArray)[itemIdx];
+            if ( ruleID != KInvalidRuleID )
+                {
+                // tag was trained successfully
+
+                item->DelayedTrainingComplete( ruleID );
+                
+                TNssSpeechItem flatItem;
+
+                flatItem.iRuleId = item->RuleID();
+                flatItem.iTagId  = item->TagId();
+
+                TInt ret = ruleIdUpdateArray.Append( flatItem );
+
+                if ( ret != KErrNone )
+                    {
+                    ruleIdUpdateArray.Close();
+                    SendTrainingCallbacks( KErrNoMemory );
+                    return;
+                    }
+                }
+            else
+                {
+                // training failed -> remove tag
+                iDatabase->DeleteTag( item->TagId() );
+                }   
+            }
+
+        aResult = iDatabase->UpdateTagRuleIDs( ruleIdUpdateArray );
+        ruleIdUpdateArray.Close();
+        SendTrainingCallbacks( aResult );
+        }
+
+    
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoSaveTags
+// Saves tags.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DoSaveTags()
+    {
+
+    __ASSERT_DEBUG( iState == ESaveStateWaiting, DEBUGPANIC );
+
+    // Save the tags en masse
+    TInt ret = iDatabase->SaveTags( iTagBuffer );
+
+    // Next, we're going to make callbacks to the client.
+    // Before that, we have to return the object to idle state,
+    // This way, the last callback can trigger new train/save action.
+
+    // Destroy buffered tags
+    TInt count = iTagBuffer->Count();
+    iTagBuffer->Close();
+    delete iTagBuffer;
+    iTagBuffer = 0;
+
+    // Detach save tag client
+    MNssSaveTagClient* client = iSaveEventHandler;
+    iSaveEventHandler = 0;
+
+    // Make SpeechItemTrainer ready for new challenges
+    SetState( EStateIdle );
+
+    // Make callbacks
+    for ( TInt k = 0; k < count; k++ )
+        {
+        if ( ret == KErrNone )
+            {
+#ifdef _DEBUG
+            TRAPD( err, client->SaveTagCompleted( KErrNone ) );
+            __ASSERT_DEBUG( err == KErrNone, DEBUGPANIC );
+#else
+            client->SaveTagCompleted( KErrNone );
+#endif // _DEBUG
+            }
+        else
+            {
+#ifdef _DEBUG
+            TRAPD( err, client->SaveTagCompleted( KErrGeneral ) );
+            __ASSERT_DEBUG( err == KErrNone, DEBUGPANIC );
+#else
+            client->SaveTagCompleted( KErrGeneral );
+#endif // _DEBUG
+            }
+        }
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoDeleteTags
+// Starts tag removal.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DoDeleteTags()
+    {
+    // NOTE: This method is never called from anywhere!
+    
+    SetState( EDeleteStateDeleting );
+
+    TInt err = CreateSrsApi();
+    if ( err != KErrNone )
+        {
+        RemoveTagsFinished( err );
+        return;
+        }
+
+    // Move Tag Buffer from filling state to processing state.
+    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
+    iSpeechItemBuffer = 0;
+    iSpeechItemBuffer = new RPointerArray<CNssSpeechItem>; // New filling buffer
+    if( !iSpeechItemBuffer )
+        {
+        SendTrainingCallbacks( KErrNoMemory );
+        return;
+        }
+
+    iTagDeleteCounter = 0;
+    DeleteNextTag();
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DoDeleteGroupedTags
+// Starts grouped tag removal.
+// Used when deleting tags without retraining.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DoDeleteGroupedTags()
+    {
+  	RUBY_DEBUG1("CNssSpeechItemTrainer::DoDeleteGroupedTags iDeleteRuleIDArray->Count() = [%d]", iDeleteRuleIDArray->Count() );
+	SetState( EDeleteStateDeleting );
+
+    // Move Tag Buffer from filling state to processing state.
+    // We want to have this buffer != NULL so that we are able to do callbacks (with appropriate error code )
+    // in RemoveTagsFinished method even if e.g. CreateSrsApi fails. 
+    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
+    iSpeechItemBuffer = 0;
+
+    TInt err = CreateSrsApi();
+    if ( err != KErrNone )
+        {
+        RemoveTagsFinished( err );
+        return;
+        }
+
+    iSpeechItemBuffer = new RPointerArray<CNssSpeechItem>; // New filling buffer
+    
+    if( !iSpeechItemBuffer )
+        {
+        // As this method is not used when retraining, no need 
+        // to send other callbacks than those related to deleting tags.
+        RemoveTagsFinished( KErrNoMemory );
+        return;
+        }
+
+	TInt ret = iSrsApi->RemoveRules( ( TSIGrammarID )iGrammarId,
+                                     *iDeleteRuleIDArray );
+
+    if ( ret != KErrNone && ret != KErrNotFound )
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::DoDeleteGroupedTags RemoveRules failed. \
+                      Error [%d]", ret );
+        RemoveTagsFinished( ret );
+        return;
+        }
+
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::DeleteNextTag
+// Sends a RemoveRule() call for the next rule in the removal queue.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::DeleteNextTag()
+    {
+    RUBY_DEBUG2( "CNssSpeechItemTrainer::DeleteNextTag iTagDeleteCounter [%d], iSpeechItemTrainingBuffer->Count() [%d]", iTagDeleteCounter, iSpeechItemTrainingBuffer->Count() );
+
+    __ASSERT_DEBUG( iTagDeleteCounter <= iSpeechItemTrainingBuffer->Count(), 
+                    User::Panic( KSpeechItemTrainerPanic, __LINE__ ) );
+
+    CNssSpeechItem* speechItem = (*iSpeechItemTrainingBuffer)[iTagDeleteCounter];
+
+    TInt ret = iSrsApi->RemoveRule(
+        (TSIGrammarID)speechItem->GrammarId(),
+        (TSIRuleID)speechItem->RuleID()
+        );
+
+    if ( ret != KErrNone && ret != KErrNotFound )
+        {
+        RemoveTagsFinished( ret );
+        return;
+        }
+    }
+
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::HandleRemoveTagComplete
+// Called after SRS has successfully removed a tag.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::HandleRemoveTagComplete()
+    {
+    iTagDeleteCounter++;
+
+    if ( iTagDeleteCounter < iSpeechItemTrainingBuffer->Count() )
+        {
+        DeleteNextTag();
+        }
+    else
+        {
+        RemoveTagsFinished( KErrNone );
+        return;
+        }
+    }
+    
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::HandleRemoveTagsComplete
+// Called after SRS has successfully removed a group of tags.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::HandleRemoveTagsComplete()
+    {
+    RemoveTagsFinished( KErrNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::HandleRemoveTagFailed
+// Called if SRS fails to remove a tag.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::HandleRemoveTagFailed( TInt aError )
+    {
+    RemoveTagsFinished( aError );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::RemoveTagsFinished
+// All required tags have been removed.
+// Go to next phase.
+//    functions a bit defferently depending on whether we were retraining or
+//    deleting tags
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::RemoveTagsFinished( TInt aSuccess )
+    {
+    // The continuation depends on what we are doing:
+
+    // If we are deleting tags
+    if ( iState == EDeleteStateDeleting )
+        {
+        if ( aSuccess == KErrNone )
+            {
+            RemoveTagsFromVAS();
+            }
+        else{
+            FinishDeleteTags( aSuccess );
+            }
+        }
+
+    // If we are retraining 
+    // (removing the old rules before adding the new ones)
+    else if ( iState == ERetrainStateRetraining )
+        {
+        if  ( aSuccess == KErrNone )
+            {
+            DoRetrainAddVoiceTags();
+            }
+        else{
+            SendTrainingCallbacks( aSuccess );
+            }
+        }
+    else
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::RemoveTagsFinished - ERROR: Wrong state(%d)", iState );
+        }
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::RemoveTagsFromVAS
+// Deletes tags from VAS.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::RemoveTagsFromVAS()
+    {
+    RArray<TUint32> tagIdArray( KRemoveTagIdBufferGranularity );
+    TInt ret = KErrNone;
+
+    ////////////// Remove tags from VAS DB ///////////////////
+
+    // Take the Tag IDs to an array.
+    for ( TInt k( 0 ); k < iSpeechItemTrainingBuffer->Count(); k++ )
+        {
+        ret |= tagIdArray.Append( (*iSpeechItemTrainingBuffer)[k]->TagId() );
+        }
+
+    // Delete tags from VAS DB
+    if ( ret == KErrNone )
+        {
+        ret = iDatabase->DeleteTags( tagIdArray );
+        }
+
+    // Close tag ID array.
+    tagIdArray.Close();
+
+    ////////////// Remove tags from VAS DB done //////////////
+
+    FinishDeleteTags( KErrNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::FinishDeleteTags
+// Cleans up and sends calbacks after
+// deleting has finished or completed.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::FinishDeleteTags( TInt aSuccess )
+    {
+    RUBY_DEBUG1("CNssSpeechItemTrainer::FinishDeleteTags aSuccess [%d]", aSuccess);
+    // Commit or uncommit according to success
+    if ( iSrsApi )
+        {
+        if ( aSuccess == KErrNone )
+            {
+            CommitSIUpdate();
+            }
+        else{
+            UncommitSIUpdate();
+            }
+        }
+	
+    // Free memory
+    TInt count( 0 );
+    if ( iSpeechItemTrainingBuffer )
+        {        
+        count = iSpeechItemTrainingBuffer->Count();
+        RUBY_DEBUG1("CNssSpeechItemTrainer::FinishDeleteTags aSuccess Freeing [%d] items from the training buffer", count);
+        iSpeechItemTrainingBuffer->Close();
+        }
+    delete iSpeechItemTrainingBuffer;
+    iSpeechItemTrainingBuffer = 0;
+
+    // Send callbacks
+    MNssDeleteTagClient* client = iDeleteEventHandler;
+    iDeleteEventHandler = 0;
+    iTagDeleteCounter = 0;
+
+    
+    for ( TInt k( 0 ); k < count; k++ )
+        {
+        RUBY_DEBUG1("CNssSpeechItemTrainer::FinishDeleteTags sending callback for item [%d]", k);
+        // Failed or succeedeed, one rule is processed
+        iDeleteRuleIDArray->Remove(0);
+       
+        client->DeleteTagCompleted( aSuccess );
+        }
+    RUBY_DEBUG2("CNssSpeechItemTrainer::FinishDeleteTags Reported [%d] deletions. [%d] remaining. Setting idle state", count, iDeleteRuleIDArray->Count());
+    SetState( EStateIdle );
+
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::RunBufferedActionsL
+// Called when it is time to execute the buffered actions
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::RunBufferedActionsL()
+    {
+    RUBY_DEBUG_BLOCK( "CNssSpeechItemTrainer::RunL" );
+    
+    if ( iState == ETrainStateWaiting )
+        {
+        DoTrainTextDelayed();
+        }
+    else if ( iState == ESaveStateWaiting )
+        {
+        DoSaveTags();
+        }
+    else if ( iState == EDeleteStateWaiting )
+        {
+        DoDeleteGroupedTags();
+        }
+    else if ( iState == ERetrainStateWaiting )
+        {
+        DoRetrainTextDelayed();
+        }
+    else
+        {
+        RUBY_DEBUG1( "CNssSpeechItemTrainer::RunL - ERROR: Wrong state (%d)", iState );
+        }
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::RestartTimer
+// Restarts the timer.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::RestartTimer()
+    {
+    TRAP_IGNORE( iActionTracker->ActionRequestedL() );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::CheckTrainingParametersL
+// All tags in the queue should have the same training parameters. This
+// function check that a new context has similar training parameters
+// as the previous ones.
+// ---------------------------------------------------------
+//
+TBool CNssSpeechItemTrainer::CheckTrainingParametersL( const CNssTrainingParameters* aParams )
+    {
+    if ( aParams )
+        {
+        if ( !iTrainingParams )
+            {
+            // Make a local copy of training parameters
+            iTrainingParams = CNssTrainingParameters::NewL();
+
+            const RArray<TLanguage>& paramLangArray = aParams->Languages();
+
+            RArray<TLanguage>* langArray = new ( ELeave ) RArray<TLanguage>;
+            CleanupStack::PushL( langArray );
+                
+            for ( TInt k = 0; k < paramLangArray.Count(); k++ )
+                {
+                TInt ret = langArray->Append( paramLangArray[ k ] );
+                if ( ret != KErrNone )
+                    {
+                    langArray->Close();
+                    delete langArray;
+                    return EFalse;
+                    }
+                }
+                
+            // Copy training languages
+            iTrainingParams->SetLanguages( langArray );
+            CleanupStack::Pop( langArray );
+
+            // Copy SINDE languages
+            TRAP_IGNORE( iTrainingParams->SetSindeLanguages( aParams->SindeLanguagesL() ) );
+            
+            // Copy separator
+            iTrainingParams->SetSeparator( aParams->Separator() );
+            }
+        else
+            {
+            // Compare with previous training parameters
+            
+            // Separator
+            if ( aParams->Separator() != iTrainingParams->Separator() )
+                {
+                return EFalse;
+                }
+
+            // Training languages
+            for ( TInt k = 0; k < aParams->Languages().Count(); k++ )
+                {
+                if ( aParams->Languages()[ k ] != iTrainingParams->Languages()[ k ] )
+                    {
+                    return EFalse;
+                    }
+                }
+            
+            // @todo SINDE languages should be checked also!            
+            }
+       }
+       
+    return ETrue;
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::CheckContext
+// All tags in the queue should have the same contexts. This
+// function check that a new context is similar to that of previous tags.
+// ---------------------------------------------------------
+//
+TBool CNssSpeechItemTrainer::CheckContext(CNssContext& aContext)
+    {
+    if ( &aContext == 0 ) // Default settings - anything goes.
+        {
+        return( EFalse );
+        }
+
+    if ( aContext.RecognitionMode() != ENSSSdSiMode &&
+         aContext.RecognitionMode() != ENSSSiMode )
+        {
+        return( EFalse );
+        }
+
+    if ( iContext )
+        {
+        // Check that identifiers (name & ID) are identical.
+        if ( aContext.ContextId() != iContext->ContextId() )
+            {
+            return( EFalse );
+            }
+
+        if ( aContext.ContextName().Compare( iContext->ContextName() ) != 0 )
+            {
+            return( EFalse );
+            }
+        }
+    else
+        {
+        if ( aContext.RecognitionMode()  == ENSSSdSiMode )
+            {
+            aContext.SetRecognitionMode( ENSSSiMode );
+            }
+
+        TRAPD( error, iContext = aContext.CopyL() );
+        if ( error != KErrNone )
+            {
+            return EFalse;
+            }
+        }
+
+    return( ETrue );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::HandleTrainComplete
+// Called after speech recognition utility has trained all tags.
+// Announces the newly assigned Rule IDs for the speech items.
+// Signals the VAS client that training has been finished.
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::HandleTrainComplete( TInt aResult )
+    {
+    RUBY_DEBUG1( "CNssSpeechItemTrainer::HandleTrainComplete(%d)", aResult );
+
+    // iRuleIdArray contains the rule IDs of the newly trained tags.
+    // The following call makes sure that
+    // iRuleIdArray.Count() == iSpeechItemTrainingBuffer.Count().
+    if ( EnforceRuleIdCountInvariant( *iRuleIDArray, 
+              iSpeechItemTrainingBuffer->Count() ) != KErrNone )
+        {
+        SendTrainingCallbacks( KErrNoMemory );
+        }
+
+    // Save rule IDs. Some rule IDs may have "KInvalidRuleID". This is a sign
+    // that training failed. It's good to save also that info to speech items.
+    for( TInt itemIdx = 0; itemIdx < iSpeechItemTrainingBuffer->Count(); itemIdx++ )
+        {
+        CNssSpeechItem* item = (*iSpeechItemTrainingBuffer)[ itemIdx ];
+        TUint32    ruleID = (*iRuleIDArray)[ itemIdx ];
+
+        item->DelayedTrainingComplete( ruleID );
+        }
+
+    SendTrainingCallbacks( aResult );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SendTrainingCallbacks
+// 
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::SendTrainingCallbacks( TInt aResult )
+    {
+    // If the changes were successful, commit them.
+    // If they werent, roll back the changes.
+    if ( aResult == KErrNone )
+        {
+        CommitSIUpdate();
+        }
+    else{
+        UncommitSIUpdate();
+        }
+
+    // Save count (callback count)...
+    TInt count = iSpeechItemTrainingBuffer->Count();
+    MNssTrainTextEventHandler* callback = iTrainEventHandler;
+    RArray<TUint32>* ruleIdArray = iRuleIDArray;
+    iRuleIDArray = 0;
+
+    // Delete buffered names that were just trained
+    if ( iSpeechItemTrainingBuffer )
+        {
+        iSpeechItemTrainingBuffer->Close();
+        }
+    delete iSpeechItemTrainingBuffer;
+    iSpeechItemTrainingBuffer = 0;
+
+
+    // If there are names on the training queue,
+    // restart the waiting of delayed training.
+    if ( iSpeechItemBuffer && iSpeechItemBuffer->Count() > 0 )
+        {
+        // don't delete context etc as training will be continued
+        SetState( ETrainStateWaiting );
+        RestartTimer();
+        }
+    else
+        {
+        SetState( EStateIdle );
+            
+        // Cleanup
+        delete iTrainingParams;
+        iTrainingParams = 0;
+
+        delete iContext;
+        iContext = 0;
+        
+        iTrainEventHandler = 0;        
+        }
+
+    // Make the callbacks to the client according to the recognition result.
+    for( TInt k( 0 ); k < count; k++ )
+        {
+        if ( ruleIdArray->Count() <= k )
+            {
+            RUBY_DEBUG2( "CNssSpeechItemTrainer::SendTrainingCallbacks ruleIdArray->Count() [%d] <= k [%d]", ruleIdArray->Count(), k );
+            callback->HandleTrainComplete( KErrNoMemory );
+            }
+        if ( (*ruleIdArray)[ k ] == KInvalidRuleID )
+            {
+            RUBY_DEBUG0( "CNssSpeechItemTrainer::SendTrainingCallbacks (*ruleIdArray)[ k ] == KInvalidRuleID" );
+            if ( aResult == KErrNone )
+                {
+                callback->HandleTrainComplete( KErrGeneral );
+                }
+            else
+                {
+                callback->HandleTrainComplete( aResult );
+                }
+            
+            }
+        else
+            {
+            callback->HandleTrainComplete( KErrNone );
+            }
+        }
+
+    ruleIdArray->Close();
+    delete ruleIdArray;
+    ruleIdArray = 0;
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::MsruoEvent
+// SRS Utility callback function 
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::MsruoEvent( TUid aEvent, TInt aResult )
+    {
+    switch( aEvent.iUid )
+        {
+        case KUidAsrEventAddVoiceTagsVal:
+            RUBY_DEBUG0( "CNssSpeechItemTrainer::MsruoEvent - state is KUidAsrEventAddVoiceTagsVal" );
+
+            if ( iState == ETrainStateTraining )
+                {
+                HandleTrainComplete( aResult );
+                }
+            else if ( iState == ERetrainStateRetraining )
+                {
+                HandleRetrainComplete( aResult );
+                }
+            break;
+
+        case KUidAsrEventRemoveRuleVal:
+            RUBY_DEBUG0( "CNssSpeechItemTrainer::MsruoEvent - state is KUidAsrEventRemoveRuleVal" );
+
+            if ( aResult == KErrNone || aResult == KErrNotFound )
+                {
+                HandleRemoveTagComplete();
+                }
+            else{
+                HandleRemoveTagFailed( aResult );
+                }
+            break;
+            
+        case KUidAsrEventRemoveRulesVal:
+            RUBY_DEBUG0( "CNssSpeechItemTrainer::MsruoEvent - state is KUidAsrEventRemoveRulesVal" );
+
+        	if( aResult == KErrNone || aResult == KErrNotFound )
+        		{
+        		HandleRemoveTagsComplete();
+        		}
+        	else 
+        		{
+        		HandleRemoveTagFailed( aResult );
+        		}
+        	break;
+
+        default:
+            RUBY_DEBUG2( "CNssSpeechItemTrainer::MsruoEvent - ERROR: Uknown state (%d,%d)", aEvent.iUid, aResult );
+            break;
+        }
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SplitPhraseSindeL
+// Splits a phrase to parts. Returns the parts in array.
+// ---------------------------------------------------------
+//
+CDesC16ArrayFlat* CNssSpeechItemTrainer::SplitPhraseSindeL( const TDesC& aPhrase, 
+                                                            TChar aSeparator )
+    {
+    CDesC16ArrayFlat* wordArray = new ( ELeave ) CDesC16ArrayFlat( KSindeWordArrayGranularity );
+    CleanupStack::PushL( wordArray );
+
+    // Separate the words in the phrase.
+    TPtrC text = aPhrase; // Cursor to the phrase
+
+    do
+        {
+        TInt index = text.Locate( aSeparator );
+
+        if ( index != 0 )
+            {
+            // no more separators -> this is the last word -> end of word == end of string
+            if ( index == KErrNotFound ) 
+                {
+                index = text.Length();
+                }
+
+                // Add word to array
+                wordArray->AppendL( text.Left( index ) );
+                }
+
+        // Discard the processed word
+        TInt charactersLeft = text.Length() - index; 
+        // Forget the separator
+        if ( charactersLeft > 0 )                    
+            {
+            charactersLeft--;                        
+            }
+
+        // Update text cursor
+        text.Set( text.Right( charactersLeft ) );
+
+        } 
+    while( text.Length() > 0 );
+
+    CleanupStack::Pop( wordArray );
+    return wordArray;
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SplitPhrase
+// Splits a phrase to parts. Returns the parts in array.
+// ---------------------------------------------------------
+//
+CDesC16ArrayFlat* CNssSpeechItemTrainer::SplitPhraseL( const TDesC& aPhrase, TChar aSeparator )
+    {
+    CDesC16ArrayFlat *wordArray = new (ELeave) CDesC16ArrayFlat( KSindeWordArrayGranularity );
+
+    // Separate the words in the phrase. Especially firstname / lastname.
+    TPtrC text = aPhrase; // Cursor to the phrase
+
+    do
+        {
+        TInt index = text.Locate( aSeparator );
+
+        // no more separators -> this is the last word -> end of word == end of string
+        if ( index == -1 ) 
+            {
+            index = text.Length();
+            }
+
+        // Add word to array
+        wordArray->AppendL( text.Left( index ) );
+
+        // Discard the processed word
+        TInt charactersLeft = text.Length() - index; 
+        // Forget the separator
+        // (the last word doesn't have one)
+        if ( charactersLeft > 0 )                    
+            {
+            charactersLeft--;                        
+            }
+
+        // Update text cursor
+        text.Set( text.Right( charactersLeft ) );
+
+        } 
+    while( text.Length() > 0 );
+
+    return wordArray;
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::CommitSIUpdate
+// Commits changes to SRS and destroys SRS utility.
+// ---------------------------------------------------------
+//
+TInt CNssSpeechItemTrainer::CommitSIUpdate()
+    {
+    if ( !iSrsApi )
+        {
+        return( KErrNotReady );
+        }
+
+    iSrsApi->CommitChanges();
+
+    delete iSrsApi;
+    iSrsApi = NULL;
+
+    return( KErrNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::UncommitSIUpdate
+// Destroys SRS utility without committing.
+// ---------------------------------------------------------
+//
+TInt CNssSpeechItemTrainer::UncommitSIUpdate()
+    {
+    if ( !iSrsApi )
+        {
+        return( KErrNotReady );
+        }
+
+    delete iSrsApi;
+    iSrsApi = NULL;
+
+    return( KErrNone );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SpeechItemBufferNeeded
+// Trys to allocate a speech item buffer
+// ---------------------------------------------------------
+//
+TBool CNssSpeechItemTrainer::SpeechItemBufferNeeded()
+    {
+    if ( !iSpeechItemBuffer )
+        {
+        iSpeechItemBuffer = new RPointerArray<CNssSpeechItem>;
+        if ( !iSpeechItemBuffer )
+            {
+            return( EFalse );
+            }
+        }
+
+    return( ETrue );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SpeechItemBufferNeeded
+// Trys to allocate a grammar id buffer
+// ---------------------------------------------------------
+//
+TBool CNssSpeechItemTrainer::GrammarIdBufferNeeded()
+    {
+    if ( !iGrammarIdBuffer )
+        {
+        iGrammarIdBuffer = new RArray<TUint32>;
+        if ( !iGrammarIdBuffer )
+            {
+            return( EFalse );
+            }
+        }
+
+    return( ETrue );
+    }
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SpeechItemBufferNeeded
+// Trys to create SRS api. Returns success status.
+// ---------------------------------------------------------
+//
+TInt CNssSpeechItemTrainer::CreateSrsApi()
+    {
+    if ( !iSrsApi )
+        {
+        TRAPD( err, iSrsApi = CNssSiUtilityWrapper::NewL( *this, KNssVASApiUid ) );
+        REACT( err, return( err ) );
+
+        iSrsApi->SetEventHandler( this );
+        }
+
+    return KErrNone;
+    }
+
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SpeechItems2Phrases
+// Converts an array of speech items
+// into Utility's AddVoiceTags array format.
+// ---------------------------------------------------------
+//
+TInt CNssSpeechItemTrainer::SpeechItems2Phrases(
+    RPointerArray<CNssSpeechItem>& aSpeechItems,
+    RPointerArray<MDesCArray>& aPhrases )
+    {
+    // iPhraseArray is an array of arrays: 
+    // 
+    //        |-- firstname1
+    //  tag1 -|-- lastname1
+    //
+    //  tag2 -|-- firstname2
+    //        |-- lastname2
+
+    // Create training parameters, if none were given
+    if ( iTrainingParams == 0 )
+        {
+        RArray<TLanguage>* languages = new RArray<TLanguage>();
+
+        if ( !languages )
+            {
+            return( KErrNoMemory );
+            }
+
+        // Use UI language + 1 other as default if languages are not given
+        // in training parameters
+        TInt err = languages->Append( User::Language() );
+
+        if ( err == KErrNone )
+            {
+            err = languages->Append( ELangOther );
+            }
+
+        if ( err != KErrNone )
+            {
+            languages->Close();
+            delete languages;
+            return( err );
+            }
+
+        TRAP( err, iTrainingParams = CNssTrainingParameters::NewL() ); 
+        if ( err != KErrNone )
+            {
+            languages->Close();
+            delete languages;
+            return( err );
+            }
+
+        iTrainingParams->SetLanguages( languages );
+        }
+
+    // Split the phrases to subwords (names -> first name + last name)
+    for ( TInt k( 0 ); k < aSpeechItems.Count(); k++ )
+        {
+        // Split names into the first name and the last name
+        CDesC16ArrayFlat *wordArray = 0;
+
+        // Check if SINDE type of training should be used
+        TRAPD( errSinde, const RArray<RTrainingLanguageArray>& temp = 
+                                        iTrainingParams->SindeLanguagesL() );
+        if ( errSinde == KErrNone )
+            {
+            TRAP_IGNORE( wordArray = SplitPhraseSindeL( aSpeechItems[ k ]->RawText(),
+                                                        iTrainingParams->Separator() ) );
+            }
+        else
+            {
+            TRAP_IGNORE( wordArray = SplitPhraseL( aSpeechItems[ k ]->Text(),
+                                                   iTrainingParams->Separator() ) );
+            }
+
+        if ( wordArray == 0 )
+            {
+            return KErrNoMemory;
+            }
+
+        TInt ret = aPhrases.Append( wordArray );
+
+        if ( ret != KErrNone )
+            {
+            return ret;
+            }
+        }
+
+    return( KErrNone );
+    }
+    
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::SetState
+// Changes state of the trainer
+// ---------------------------------------------------------
+//
+void CNssSpeechItemTrainer::SetState(TTrainState aState) 
+	{
+	RUBY_DEBUG2("CNssSpeechItemTrainer::SetState Switching state from [%d] to [%d]", iState, aState);
+#ifdef _DEBUG
+	if( iState == EDeleteStateWaiting ) 
+		{
+		RUBY_DEBUG0("CNssSpeechItemTrainer::SetState From EDeleteStateWaiting");
+		}
+	if( aState == EDeleteStateWaiting ) 
+		{
+		RUBY_DEBUG0("CNssSpeechItemTrainer::SetState To EDeleteStateWaiting");
+		}
+		
+#endif	
+	iState = aState;
+	}
+
+// ---------------------------------------------------------
+// CNssSpeechItemTrainer::EnforceRuleIdCountInvariant
+// Adds KInvalidRuleId to the Rule ID Array, until
+// the number of rules is equal to count.
+// ---------------------------------------------------------
+//
+TInt EnforceRuleIdCountInvariant( RArray<TUint32>& aArray, TInt aCount )
+    {
+    while( aArray.Count() < aCount )
+        {
+        TInt err = aArray.Append( KInvalidRuleID );
+        if ( err != KErrNone )
+            {
+            return( err );
+            }
+        }
+
+    return KErrNone;
+    }