cbsatplugin/atmisccmdplugin/src/scpbwcommandhandler.cpp
branchRCL_3
changeset 72 4b59561a31c0
parent 64 1934667b0e2b
equal deleted inserted replaced
64:1934667b0e2b 72:4b59561a31c0
     1 /*
       
     2  * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     3  * All rights reserved.
       
     4  * This component and the accompanying materials are made available
       
     5  * under the terms of "Eclipse Public License v1.0"
       
     6  * which accompanies this distribution, and is available
       
     7  * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8  * Initial Contributors:
       
     9  * Nokia Corporation - initial contribution.
       
    10  *
       
    11  * Contributors:
       
    12  * Description :
       
    13  *
       
    14  */
       
    15 
       
    16 #include "scpbwcommandhandler.h"
       
    17 #include <mpbutil.h> 
       
    18 #include <exterror.h>
       
    19 #include <etelmmerr.h>
       
    20 
       
    21 #include "atmisccmdpluginconsts.h"
       
    22 #include "debug.h"
       
    23 
       
    24 const TInt KMaxContactEntrySize = 512;
       
    25 const TInt KMaxTextLength = 64;
       
    26 const TInt KMaxNumberLength = 64;
       
    27 const TInt KMaxEmailLength = 64;
       
    28 
       
    29 CSCPBWCommandHandler* CSCPBWCommandHandler::NewL(MATMiscCmdPlugin* aCallback, TAtCommandParser& aATCmdParser, RMobilePhone& aPhone)
       
    30     {
       
    31     TRACE_FUNC_ENTRY
       
    32     CSCPBWCommandHandler* self = new (ELeave) CSCPBWCommandHandler(aCallback, aATCmdParser, aPhone);
       
    33     CleanupStack::PushL(self);
       
    34     self->ConstructL();
       
    35     CleanupStack::Pop(self);
       
    36     TRACE_FUNC_EXIT
       
    37     return self;
       
    38     }
       
    39 
       
    40 CSCPBWCommandHandler::CSCPBWCommandHandler(MATMiscCmdPlugin* aCallback, TAtCommandParser& aATCmdParser, RMobilePhone& aPhone) :
       
    41     CATCmdAsyncBase(aCallback, aATCmdParser, aPhone)
       
    42     {
       
    43     TRACE_FUNC_ENTRY
       
    44     TRACE_FUNC_EXIT
       
    45     }
       
    46 
       
    47 void CSCPBWCommandHandler::ConstructL()
       
    48     {
       
    49     TRACE_FUNC_ENTRY
       
    50     
       
    51     TInt err = iPhoneBookStore.Open(iPhone, KETelIccAdnPhoneBook);
       
    52     if (err != KErrNone)
       
    53         {
       
    54         iState = ESCPBWStateSimStoreNotSupported;
       
    55         TRACE_FUNC_EXIT
       
    56         return;
       
    57         }
       
    58     err = iMmCustomAPI.Open(iPhone);
       
    59     if (err != KErrNone)
       
    60         {
       
    61         iPhoneBookStore.Close();
       
    62         iState = ESCPBWStateSimStoreNotSupported;
       
    63         TRACE_FUNC_EXIT
       
    64         return;
       
    65         }
       
    66     iPhoneBookBuffer = new (ELeave) CPhoneBookBuffer;
       
    67     
       
    68     iReply.CreateL(KDefaultCmdBufLength);
       
    69     iNum1.CreateL(KMaxNumberLength);
       
    70     iNum2.CreateL(KMaxNumberLength);
       
    71     iNum3.CreateL(KMaxNumberLength);
       
    72     iNum4.CreateL(KMaxNumberLength);
       
    73     iText.CreateL(KMaxTextLength);
       
    74     iEmail.CreateL(KMaxEmailLength);
       
    75     iPbData.CreateL(KMaxContactEntrySize);
       
    76     
       
    77     TRACE_FUNC_EXIT
       
    78     }
       
    79 
       
    80 CSCPBWCommandHandler::~CSCPBWCommandHandler()
       
    81     {
       
    82     TRACE_FUNC_ENTRY
       
    83     Cancel();
       
    84     delete iPhoneBookBuffer;
       
    85     iPhoneBookStore.Close();
       
    86     iMmCustomAPI.Close();
       
    87     iPbData.Close();
       
    88     iReply.Close();
       
    89     iNum1.Close();
       
    90     iNum2.Close();
       
    91     iNum3.Close();
       
    92     iNum4.Close();
       
    93     iText.Close();
       
    94     iEmail.Close();
       
    95     TRACE_FUNC_EXIT
       
    96     }
       
    97 
       
    98 void CSCPBWCommandHandler::HandleCommand(const TDesC8& /*aCmd*/, RBuf8& /*aReply*/, TBool /*aReplyNeeded*/)
       
    99     {
       
   100     TRACE_FUNC_ENTRY
       
   101     
       
   102     if (iState != ESCPBWStateIdle)
       
   103         {
       
   104         // Reply "ERROR" if handler is not in idle
       
   105         iCallback->CreateReplyAndComplete(EReplyTypeError);
       
   106         TRACE_FUNC_EXIT
       
   107         return;
       
   108         }
       
   109     TInt err = KErrNone;
       
   110     
       
   111     TAtCommandParser::TCommandHandlerType cmdHandlerType = iATCmdParser.CommandHandlerType();
       
   112     
       
   113     switch (cmdHandlerType)
       
   114         {
       
   115         case (TAtCommandParser::ECmdHandlerTypeTest):
       
   116             {
       
   117             if (iMaxEntries == 0)
       
   118                 {
       
   119                 RMobilePhoneBookStore::TMobilePhoneBookInfoV1Pckg pckg(iPhoneBookStoreInfo);
       
   120                 iPhoneBookStore.GetInfo(iStatus, pckg);
       
   121                 iState = ESCPBWStateGetPhonebookInfo;
       
   122                 SetActive();
       
   123                 }
       
   124             else if (iEmailLength == 0)
       
   125                 {
       
   126                 iState = ESCPBWStateGet3GPBInfo;
       
   127                 iMmCustomAPI.Get3GPBInfo(iStatus, i3GPBInfo);
       
   128                 SetActive();
       
   129                 }
       
   130             else
       
   131                 {
       
   132                 // Phonebook info has been obtained
       
   133                 iReply.Zero();
       
   134                 iReply.Format(KSCPBWSupportedCmdsList, iMaxEntries, iNumLength, iTextLength, iEmailLength);
       
   135                 iCallback->CreateReplyAndComplete( EReplyTypeOk, iReply );
       
   136                 }
       
   137             }
       
   138             break;
       
   139         case (TAtCommandParser::ECmdHandlerTypeSet): 
       
   140             {
       
   141             ResetParameters();
       
   142             TBool isDeleteRequest = EFalse;
       
   143             err = ParseParameters(isDeleteRequest);
       
   144             Trace(_L("Parse completed, err = %d"), err);
       
   145             if (isDeleteRequest)
       
   146                 {
       
   147                 // Delete entry at iIndex
       
   148                 iState = ESCPBWStateDelete;
       
   149                 iPhoneBookStore.Delete(iStatus, iIndex);
       
   150                 SetActive();
       
   151                 }
       
   152             else if (err == KErrNone)
       
   153                 {
       
   154                 // Create an entry
       
   155                 err = CreateContactEntry();
       
   156                 if (err == KErrNone)
       
   157                     {
       
   158                     iPhoneBookStore.Write(iStatus, iPbData, iIndex);
       
   159                     iState = ESCPBWStateWrite;
       
   160                     SetActive();
       
   161                     }
       
   162                 else
       
   163                     {
       
   164                     iCallback->CreateReplyAndComplete( EReplyTypeError );
       
   165                     }
       
   166                 }
       
   167             else
       
   168                 {
       
   169                 iCallback->CreateReplyAndComplete( EReplyTypeError );
       
   170                 }
       
   171             break;
       
   172             }
       
   173         default:
       
   174             {
       
   175             iCallback->CreateReplyAndComplete( EReplyTypeError );
       
   176             break;
       
   177             }
       
   178         }
       
   179     
       
   180     TRACE_FUNC_EXIT
       
   181     }
       
   182 
       
   183 void CSCPBWCommandHandler::RunL()
       
   184     {
       
   185     TRACE_FUNC_ENTRY
       
   186     
       
   187     iReply.Zero();
       
   188     TInt err = iStatus.Int();
       
   189     Trace(_L("State = %d, err = %d"), iState, err);
       
   190     
       
   191     if (err == KErrNone)
       
   192         {
       
   193         switch (iState)
       
   194             {
       
   195             case ESCPBWStateGetPhonebookInfo:
       
   196                 {
       
   197                 Trace(_L("Get info successful."));
       
   198                 iNumLength = iPhoneBookStoreInfo.iMaxNumLength;
       
   199                 iTextLength = iPhoneBookStoreInfo.iMaxTextLength;
       
   200                 iMaxEntries = iPhoneBookStoreInfo.iTotalEntries;
       
   201                 if (iEmailLength == 0)
       
   202                     {
       
   203                     iMmCustomAPI.Get3GPBInfo(iStatus, i3GPBInfo);
       
   204                     iState = ESCPBWStateGet3GPBInfo;
       
   205                     SetActive();
       
   206                     }
       
   207                 else
       
   208                     {
       
   209                     iReply.Format(KSCPBWSupportedCmdsList, iMaxEntries, iNumLength, iTextLength, iEmailLength);
       
   210                     iCallback->CreateReplyAndComplete(EReplyTypeOk, iReply);
       
   211                     iState = ESCPBWStateIdle;
       
   212                     }
       
   213                 }
       
   214                 break;
       
   215             case ESCPBWStateGet3GPBInfo:
       
   216                 {
       
   217                 Trace(_L("Get 3GPB info successful."));
       
   218                 iEmailLength = i3GPBInfo.iMaxLenEmail;
       
   219                 iReply.Format(KSCPBWSupportedCmdsList, iMaxEntries, iNumLength, iTextLength, iEmailLength);
       
   220                 iCallback->CreateReplyAndComplete(EReplyTypeOk, iReply);
       
   221                 iState = ESCPBWStateIdle;
       
   222                 }
       
   223                 break;
       
   224             case ESCPBWStateWrite:
       
   225                 {
       
   226                 Trace(_L("Write successful. Index = %d"), iIndex);
       
   227                 iCallback->CreateReplyAndComplete(EReplyTypeOk);
       
   228                 iState = ESCPBWStateIdle;
       
   229                 }
       
   230                 break;
       
   231             case ESCPBWStateDelete:
       
   232                 {
       
   233                 Trace(_L("Delete successful."));
       
   234                 iCallback->CreateReplyAndComplete(EReplyTypeOk);
       
   235                 iState = ESCPBWStateIdle;
       
   236                 }
       
   237                 break;
       
   238             default:
       
   239                 {
       
   240                 iState = ESCPBWStateIdle;
       
   241                 iCallback->CreateReplyAndComplete(EReplyTypeError);
       
   242                 break;
       
   243                 }
       
   244             }
       
   245         }
       
   246     else
       
   247         {
       
   248         iState = ESCPBWStateIdle; 
       
   249         iCallback->CreateCMEReplyAndComplete(err);
       
   250         }
       
   251     
       
   252     TRACE_FUNC_EXIT
       
   253     }
       
   254 
       
   255 void CSCPBWCommandHandler::DoCancel() 
       
   256     {
       
   257     TRACE_FUNC_ENTRY
       
   258     
       
   259     switch (iState)
       
   260         {
       
   261         case ESCPBWStateGetPhonebookInfo:
       
   262             {
       
   263             iPhoneBookStore.CancelAsyncRequest(EMobilePhoneStoreGetInfo);
       
   264             }
       
   265             break;
       
   266         case ESCPBWStateGet3GPBInfo:
       
   267             {
       
   268             iMmCustomAPI.CancelAsyncRequest(EGet3GPBInfoIPC);
       
   269             }
       
   270             break;
       
   271         case ESCPBWStateDelete:
       
   272             {
       
   273             iPhoneBookStore.CancelAsyncRequest(EMobilePhoneStoreDelete);
       
   274             }
       
   275             break;
       
   276         case ESCPBWStateWrite:
       
   277             {
       
   278             iPhoneBookStore.CancelAsyncRequest(EMobilePhoneStoreWrite);
       
   279             }
       
   280             break;
       
   281         }
       
   282     iState = ESCPBWStateIdle;
       
   283     
       
   284     TRACE_FUNC_EXIT
       
   285     }
       
   286 
       
   287 TInt CSCPBWCommandHandler::ParseParameters( TBool& aIsDeleteRequest )
       
   288     {
       
   289     TRACE_FUNC_ENTRY
       
   290     
       
   291     TInt ret = KErrNone;
       
   292     // Paese index
       
   293     ret = iATCmdParser.NextIntParam(iIndex); 
       
   294     Trace(_L("Parse index err: %d"), ret);
       
   295     Trace(_L("index: %d"), iIndex);
       
   296     if (ret != KErrNone && ret != KErrNotFound)
       
   297         {
       
   298         // Bad index
       
   299         TRACE_FUNC_EXIT
       
   300         return KErrArgument;
       
   301         }
       
   302     
       
   303     TPtrC8 ptrc;
       
   304     // Parse num1
       
   305     ptrc.Set(iATCmdParser.NextParam());
       
   306     if (ptrc.Length() != 0)
       
   307         {
       
   308         Trace(_L("Parse num1 OK: %S"), &ptrc);
       
   309         SetBuffer(iNum1, ptrc);
       
   310         }
       
   311     else if (ret == KErrNone )
       
   312         {
       
   313         // Only index given
       
   314         Trace(_L("Only index given."));
       
   315         aIsDeleteRequest = ETrue;
       
   316         TRACE_FUNC_EXIT
       
   317         return KErrNone;
       
   318         }
       
   319     else 
       
   320         {
       
   321         // no num1 found
       
   322         TRACE_FUNC_EXIT
       
   323         return KErrArgument;
       
   324         }
       
   325     ret = iATCmdParser.NextIntParam(iType1);
       
   326     if (ret == KErrNotFound)
       
   327         {
       
   328         TRACE_FUNC_EXIT
       
   329         return KErrNone;
       
   330         }
       
   331     else if (ret != KErrNone)
       
   332         {
       
   333         TRACE_FUNC_EXIT
       
   334         return KErrArgument;
       
   335         }
       
   336     
       
   337     // Parse num2
       
   338     ptrc.Set(iATCmdParser.NextParam());
       
   339     if (ptrc.Length() != 0)
       
   340         {
       
   341         Trace(_L("Parse num2 OK: %S"), &ptrc);
       
   342         SetBuffer(iNum2, ptrc);
       
   343         }
       
   344     else 
       
   345         {
       
   346         // no num2 found
       
   347         TRACE_FUNC_EXIT
       
   348         return KErrNone;
       
   349         }
       
   350     ret = iATCmdParser.NextIntParam(iType2);
       
   351     if (ret == KErrNotFound)
       
   352         {
       
   353         TRACE_FUNC_EXIT
       
   354         return KErrNone;
       
   355         }
       
   356     else if (ret != KErrNone)
       
   357         {
       
   358         TRACE_FUNC_EXIT
       
   359         return KErrArgument;
       
   360         }
       
   361     
       
   362     // Parse num3
       
   363     ptrc.Set(iATCmdParser.NextParam());
       
   364     if (ptrc.Length() != 0)
       
   365         {
       
   366         Trace(_L("Parse num3 OK: %S"), &ptrc);
       
   367         SetBuffer(iNum3, ptrc);
       
   368         }
       
   369     else
       
   370         {
       
   371         // no num3 found
       
   372         TRACE_FUNC_EXIT
       
   373         return KErrNone;
       
   374         }
       
   375     ret = iATCmdParser.NextIntParam(iType3);
       
   376     if (ret == KErrNotFound)
       
   377         {
       
   378         TRACE_FUNC_EXIT
       
   379         return KErrNone;
       
   380         }
       
   381     else if (ret != KErrNone)
       
   382         {
       
   383         TRACE_FUNC_EXIT
       
   384         return KErrArgument;
       
   385         }
       
   386     
       
   387     // Parse num4
       
   388     ptrc.Set(iATCmdParser.NextParam());
       
   389     if (ptrc.Length() != 0)
       
   390         {
       
   391         Trace(_L("Parse num4 OK: %S"), &ptrc);
       
   392         SetBuffer(iNum4, ptrc);
       
   393         }
       
   394     else
       
   395         {
       
   396         // no num4 found
       
   397         TRACE_FUNC_EXIT
       
   398         return KErrNone;
       
   399         }
       
   400     ret = iATCmdParser.NextIntParam(iType4);
       
   401     if (ret == KErrNotFound)
       
   402         {
       
   403         TRACE_FUNC_EXIT
       
   404         return KErrNone;
       
   405         }
       
   406     else if (ret != KErrNone)
       
   407         {
       
   408         TRACE_FUNC_EXIT
       
   409         return KErrArgument;
       
   410         }
       
   411     
       
   412     // Parse text
       
   413     ptrc.Set(iATCmdParser.NextParam());
       
   414     if (ptrc.Length() != 0)
       
   415         {
       
   416         Trace(_L("Parse text OK: %S"), &ptrc);
       
   417         SetBuffer(iText, ptrc);
       
   418         }
       
   419     else
       
   420         {
       
   421         // no text found
       
   422         TRACE_FUNC_EXIT
       
   423         return KErrNone;
       
   424         }
       
   425     ret = iATCmdParser.NextIntParam(iCoding);
       
   426     if (ret == KErrNotFound)
       
   427         {
       
   428         TRACE_FUNC_EXIT
       
   429         return KErrNone;
       
   430         }
       
   431     else if (ret != KErrNone)
       
   432         {
       
   433         TRACE_FUNC_EXIT
       
   434         return KErrArgument;
       
   435         }
       
   436     
       
   437     // Parse email
       
   438     ptrc.Set(iATCmdParser.NextParam());
       
   439     if (ptrc.Length() != 0)
       
   440         {
       
   441         Trace(_L("Parse email OK: %S"), &ptrc);
       
   442         SetBuffer(iEmail, ptrc);
       
   443         }
       
   444     else
       
   445         {
       
   446         // no email found
       
   447         TRACE_FUNC_EXIT
       
   448         return KErrNone;
       
   449         }
       
   450     
       
   451     if (iATCmdParser.NextParam().Length() != 0)
       
   452         {
       
   453         // too many parameters
       
   454         Trace(_L("Too many parameters."));
       
   455         TRACE_FUNC_EXIT
       
   456         return KErrArgument;
       
   457         }
       
   458     
       
   459     TRACE_FUNC_EXIT
       
   460     return KErrNone;
       
   461     }
       
   462 
       
   463 TInt CSCPBWCommandHandler::CreateContactEntry()
       
   464     {
       
   465     TRACE_FUNC_ENTRY
       
   466     
       
   467     TInt err = KErrNone;
       
   468     iPhoneBookBuffer->Set(&iPbData);
       
   469     // Add new entry tag
       
   470     err = iPhoneBookBuffer->AddNewEntryTag();
       
   471     Trace(_L("New entry tag added, err = %d"), err);
       
   472     if (err != KErrNone)
       
   473         {
       
   474         TRACE_FUNC_EXIT
       
   475         return err;
       
   476         }
       
   477     // Put index into the entry
       
   478     err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBAdnIndex, (TUint16)iIndex);
       
   479     Trace(_L("Index added, err = %d"), err);
       
   480     if (err != KErrNone)
       
   481         {
       
   482         TRACE_FUNC_EXIT
       
   483         return err;
       
   484         }
       
   485     // Put text into the entry
       
   486     if (iText.Length() != 0)
       
   487         {
       
   488         err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBText, iText);
       
   489         Trace(_L("Text added, err = %d"), err);
       
   490         if (err != KErrNone)
       
   491             {
       
   492             TRACE_FUNC_EXIT
       
   493             return err;
       
   494             }
       
   495         }
       
   496     // Put num1 into the entry
       
   497     if (iNum1.Length() != 0)
       
   498         {
       
   499         err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBNumber, iNum1);
       
   500         Trace(_L("Number 1 added, err = %d"), err);
       
   501         if (err != KErrNone)
       
   502             {
       
   503             TRACE_FUNC_EXIT
       
   504             return err;
       
   505             }
       
   506         }
       
   507     // Put num2 into the entry
       
   508     if (iNum2.Length() != 0)
       
   509         {
       
   510         // Add anr tag 
       
   511         err = iPhoneBookBuffer->AddNewNumberTag();
       
   512         Trace(_L("New number tag added, err = %d"), err);
       
   513         if (err != KErrNone)
       
   514             {
       
   515             TRACE_FUNC_EXIT
       
   516             return err;
       
   517             }
       
   518         err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBNumber, iNum2);
       
   519         Trace(_L("Number 2 added, err = %d"), err);
       
   520         if (err != KErrNone)
       
   521             {
       
   522             TRACE_FUNC_EXIT
       
   523             return err;
       
   524             }
       
   525         }
       
   526     // Put num3 into the entry
       
   527     if (iNum3.Length() != 0)
       
   528         {
       
   529         // Add anr tag 
       
   530         err = iPhoneBookBuffer->AddNewNumberTag();
       
   531         Trace(_L("New number tag added, err = %d"), err);
       
   532         if (err != KErrNone)
       
   533             {
       
   534             TRACE_FUNC_EXIT
       
   535             return err;
       
   536             }
       
   537         err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBNumber, iNum3);
       
   538         Trace(_L("Number 3 added, err = %d"), err);
       
   539         if (err != KErrNone)
       
   540             {
       
   541             TRACE_FUNC_EXIT
       
   542             return err;
       
   543             }
       
   544         }
       
   545     // Put num4 into the entry
       
   546     if (iNum4.Length() != 0)
       
   547         {
       
   548         // Add anr tag 
       
   549         err = iPhoneBookBuffer->AddNewNumberTag();
       
   550         Trace(_L("New number tag added, err = %d"), err);
       
   551         if (err != KErrNone)
       
   552             {
       
   553             TRACE_FUNC_EXIT
       
   554             return err;
       
   555             }
       
   556         err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBNumber, iNum4);
       
   557         Trace(_L("Number 4 added, err = %d"), err);
       
   558         if (err != KErrNone)
       
   559             {
       
   560             TRACE_FUNC_EXIT
       
   561             return err;
       
   562             }
       
   563         }    
       
   564     // Put email address into the entry
       
   565     if (iEmail.Length() != 0)
       
   566         {
       
   567         err = iPhoneBookBuffer->PutTagAndValue(RMobilePhoneBookStore::ETagPBEmailAddress, iEmail);
       
   568         Trace(_L("Email added, err = %d"), err);
       
   569         if (err != KErrNone)
       
   570             {
       
   571             TRACE_FUNC_EXIT
       
   572             return err;
       
   573             }
       
   574         }
       
   575     
       
   576     TRACE_FUNC_EXIT
       
   577     return KErrNone;
       
   578     }
       
   579 
       
   580 void CSCPBWCommandHandler::ResetParameters()
       
   581     {
       
   582     TRACE_FUNC_ENTRY
       
   583     
       
   584     iPbData.Zero();
       
   585     iIndex = -1;
       
   586     iNum1.Zero();
       
   587     iType1 = 0x91; // International & ISDN
       
   588     iNum2.Zero();
       
   589     iType2 = 0x91;
       
   590     iNum3.Zero();
       
   591     iType3 = 0x91;
       
   592     iNum4.Zero();
       
   593     iType4 = 0x91;
       
   594     iText.Zero();
       
   595     iCoding = 0; // GSM 7 bit
       
   596     iEmail.Zero();
       
   597     
       
   598     TRACE_FUNC_EXIT
       
   599     }
       
   600 
       
   601 void CSCPBWCommandHandler::SetBuffer(TDes& aDest, const TDesC8& aSource)
       
   602     {
       
   603     TRACE_FUNC_ENTRY
       
   604     TInt maxLength = aDest.MaxLength();
       
   605     if (aSource.Length() <= maxLength)
       
   606         {
       
   607         aDest.Copy(aSource);
       
   608         }
       
   609     else
       
   610         {
       
   611         aDest.Copy(aSource.Left(maxLength));
       
   612         }
       
   613     TRACE_FUNC_EXIT
       
   614     }
       
   615 
       
   616