profilesservices/MediaFileList/Src/mediafileprotection.cpp
branchRCL_3
changeset 54 7e0eff37aedb
child 67 940cad2b4a51
equal deleted inserted replaced
53:8ee96d21d9bf 54:7e0eff37aedb
       
     1 /*
       
     2 * Copyright (c) 2007 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 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description:   Class used to check the protection of the 
       
    15 *                media files.
       
    16 *
       
    17 */
       
    18 
       
    19 
       
    20 
       
    21 
       
    22 // INCLUDE FILES
       
    23 
       
    24 #include "mediafileprotection.h"
       
    25 #include "mediafilelist.h"
       
    26 #include "mediafilelistdebug.h"
       
    27 #include <mediafilelist.rsg>
       
    28 
       
    29 #include <DRMCommon.h>
       
    30 #include <drmutility.h>
       
    31 #include <drmagents.h>
       
    32 #include <bautils.h>
       
    33 #include <StringLoader.h>
       
    34 
       
    35 
       
    36 
       
    37 /******************************************************************************
       
    38  * class CMFProtectionHandler
       
    39  ******************************************************************************/
       
    40 
       
    41 // -----------------------------------------------------------------------------
       
    42 // CMFProtectionHandler::NewL
       
    43 //
       
    44 // -----------------------------------------------------------------------------
       
    45 CMFProtectionHandler* CMFProtectionHandler::NewL()
       
    46     {
       
    47     CMFProtectionHandler* self = new (ELeave) CMFProtectionHandler();
       
    48     CleanupStack::PushL(self);
       
    49     self->ConstructL();
       
    50     CleanupStack::Pop(self);
       
    51 
       
    52     return self;
       
    53     }
       
    54 
       
    55 
       
    56 // -----------------------------------------------------------------------------
       
    57 // CMFProtectionHandler::CMFProtectionHandler
       
    58 // 
       
    59 // -----------------------------------------------------------------------------
       
    60 //
       
    61 CMFProtectionHandler::CMFProtectionHandler()
       
    62 	{
       
    63 	iMaxFileSize = KErrNotFound;
       
    64     }
       
    65 
       
    66 
       
    67 // -----------------------------------------------------------------------------
       
    68 // CMFProtectionHandler::CMFProtectionHandler
       
    69 //
       
    70 // -----------------------------------------------------------------------------
       
    71 //
       
    72 void CMFProtectionHandler::ConstructL()
       
    73     {
       
    74     iDriveUtil = CDriveUtil::NewL();
       
    75     iVariation = CMediaFileDialogVariation::NewL();
       
    76 
       
    77     iDRMHelper = CDRMHelper::NewL( *CCoeEnv::Static() );
       
    78     
       
    79     iDRMCommon = DRMCommon::NewL();
       
    80     User::LeaveIfError( iDRMCommon->Connect() );
       
    81     
       
    82     iExcludedMimeTypes = CMimeTypeList::NewL();
       
    83     
       
    84     User::LeaveIfError( iApaLsSession.Connect() );
       
    85     User::LeaveIfError( iFsSession.Connect() );
       
    86     }
       
    87 
       
    88 
       
    89 // ----------------------------------------------------------------------------
       
    90 // Destructor
       
    91 //
       
    92 // ----------------------------------------------------------------------------
       
    93 //
       
    94 CMFProtectionHandler::~CMFProtectionHandler()
       
    95     {
       
    96     delete iDriveUtil;
       
    97     delete iVariation;
       
    98     
       
    99     delete iDRMHelper;
       
   100     
       
   101     if ( iDRMCommon )
       
   102         {
       
   103         iDRMCommon->Disconnect(); // ignore possible error
       
   104         delete iDRMCommon;
       
   105         }
       
   106     
       
   107     delete iExcludedMimeTypes;
       
   108     iApaLsSession.Close();
       
   109     iFsSession.Close();
       
   110     }
       
   111 
       
   112 
       
   113 // -----------------------------------------------------------------------------
       
   114 // CMFProtectionHandler::SetAttr
       
   115 // 
       
   116 // -----------------------------------------------------------------------------
       
   117 void CMFProtectionHandler::SetAttrL( TInt aAttr, TInt aValue )
       
   118     {
       
   119     switch ( aAttr )
       
   120         {
       
   121         case CMediaFileList::EAttrFileSize:
       
   122             {
       
   123             iMaxFileSize = aValue;
       
   124             break;
       
   125             }
       
   126         case CMediaFileList::EAttrAutomatedType:
       
   127             {
       
   128             iAutomatedType = (CDRMHelper::TDRMHelperAutomatedType) aValue;
       
   129             break;
       
   130             }
       
   131 
       
   132         default:
       
   133             {
       
   134             break;
       
   135             }
       
   136         }
       
   137     }
       
   138 
       
   139 
       
   140 // -----------------------------------------------------------------------------
       
   141 // CMFProtectionHandler::SetAttrL
       
   142 // 
       
   143 // -----------------------------------------------------------------------------
       
   144 void CMFProtectionHandler::SetAttrL( TInt aAttr, const TDesC& aValue )
       
   145     {
       
   146     if ( aAttr == CMediaFileList::EAttrExcludeMimeType )
       
   147         {
       
   148         if ( aValue.Length() > KMaxFileName || aValue.Length() == 0 )
       
   149             {
       
   150             User::Leave( KErrArgument );
       
   151             }
       
   152             
       
   153         iExcludedMimeTypes->AddMimeTypeL( aValue );
       
   154         }
       
   155     }
       
   156 
       
   157 
       
   158 // -----------------------------------------------------------------------------
       
   159 // CMFProtectionHandler::IsVideoValid
       
   160 // 
       
   161 // -----------------------------------------------------------------------------
       
   162 //
       
   163 TBool CMFProtectionHandler::IsVideoValid( const TDesC& aFileName,
       
   164                                          TIntention aIntention )
       
   165     {
       
   166     TInt err = KErrNone;
       
   167     TBool ret = EFalse;
       
   168     
       
   169     TRAP( err, ret = IsVideoValidL (aFileName, aIntention ) )
       
   170     
       
   171     if ( err != KErrNone )
       
   172         {
       
   173         return EFalse;  // in case of error file is not valid
       
   174         }
       
   175     
       
   176     return ret;
       
   177     }
       
   178 
       
   179 
       
   180 // -----------------------------------------------------------------------------
       
   181 // CMFProtectionHandler::IsVideoValidL
       
   182 // 
       
   183 // -----------------------------------------------------------------------------
       
   184 //
       
   185 TBool CMFProtectionHandler::IsVideoValidL( const TDesC& aFileName,
       
   186                                           TIntention /*aIntention*/ )
       
   187     {
       
   188     TBuf<KMaxDataTypeLength> dataType( DataTypeL( aFileName ).Des() );
       
   189   
       
   190     if ( iExcludedMimeTypes->FindMimeTypeL( dataType ) )
       
   191         {
       
   192         return EFalse;
       
   193         }
       
   194 
       
   195     CContent* content = CContent::NewLC( aFileName,
       
   196                                          EContentShareReadWrite );
       
   197     TInt deliveryMethod = 0;
       
   198     content->GetAttribute( EDeliveryMethod, deliveryMethod );
       
   199     
       
   200     CleanupStack::PopAndDestroy( content );
       
   201     
       
   202     if( deliveryMethod == EOmaDrm2 )
       
   203         {
       
   204         // if video it is OMA DRM 2 protected, it cannot be previewed
       
   205         return EFalse;
       
   206         }
       
   207     return ETrue;
       
   208     }
       
   209 
       
   210 
       
   211 // -----------------------------------------------------------------------------
       
   212 // CMFProtectionHandler::IsFileValid
       
   213 // 
       
   214 // Checks protection state of media file. If this function returns EFalse,
       
   215 // media file cannot be used for playing/selection.
       
   216 //
       
   217 // -----------------------------------------------------------------------------
       
   218 //
       
   219 TBool CMFProtectionHandler::IsFileValid( const TDesC& aFileName,
       
   220                                          TIntention aIntention )
       
   221     {
       
   222     TInt err = KErrNone;
       
   223     TBool ret = EFalse;
       
   224     
       
   225     TRAP( err, ret = IsFileValidL (aFileName, aIntention ) )
       
   226     
       
   227     if ( err != KErrNone )
       
   228         {
       
   229         return EFalse;  // in case of error file is not valid
       
   230         }
       
   231     
       
   232     return ret;
       
   233     }
       
   234 
       
   235 
       
   236 // -----------------------------------------------------------------------------
       
   237 // CMFProtectionHandler::IsFlieDRMExpired
       
   238 // 
       
   239 // Check if the DRM protect file is expired or have no rights.
       
   240 // -----------------------------------------------------------------------------
       
   241 //
       
   242 TBool CMFProtectionHandler::IsFlieDRMExpired( const TDesC& aFileName )
       
   243     {
       
   244     // Check whether the file can be set as automated content or not
       
   245     TBool canSetAutomated = EFalse;
       
   246     TInt canSetAutomatedErr = iDRMHelper->CanSetAutomated( aFileName, canSetAutomated );
       
   247     
       
   248     // Check if rights expried or no rights to use
       
   249     if ( canSetAutomatedErr == DRMCommon::ERightsExpired ||
       
   250         canSetAutomatedErr == DRMCommon::ENoRights )
       
   251         {
       
   252         return ETrue;
       
   253         }
       
   254     return EFalse;
       
   255     }
       
   256 
       
   257 
       
   258 // -----------------------------------------------------------------------------
       
   259 // CMFProtectionHandler::IsFileValidL
       
   260 // 
       
   261 // Function is copy from CFLDDRMImplementation.cpp.
       
   262 // -----------------------------------------------------------------------------
       
   263 //
       
   264 TBool CMFProtectionHandler::IsFileValidL( const TDesC& aFileName, 
       
   265                                           TIntention aIntention )
       
   266     {
       
   267     TInt err = KErrNone;
       
   268     TBuf<KMaxDataTypeLength> dataType( DataTypeL( aFileName ).Des() );
       
   269     
       
   270     if ( aFileName.Length() == 0 )
       
   271         {
       
   272         return ETrue;  // empty filename is valid??
       
   273         }
       
   274  
       
   275     if ( iExcludedMimeTypes->FindMimeTypeL( dataType ) )
       
   276         {
       
   277         ShowErrorNoteL( R_QTN_INFO_FILE_FORMAT_ERROR );
       
   278         return EFalse;
       
   279         }
       
   280     
       
   281     if ( aIntention == CMFProtectionHandler::ESelect && 
       
   282         !CheckFileSize( aFileName, dataType ) )
       
   283         {
       
   284         HBufC* hBuf = StringLoader::LoadLC( R_QTN_INFO_FILE_SIZE_ERROR, iMaxFileSize );
       
   285         TMFDialogUtil::ShowInformationNoteL( *hBuf );
       
   286         CleanupStack::PopAndDestroy( hBuf );
       
   287         return EFalse;
       
   288         }
       
   289 
       
   290     if ( iDriveUtil->IsRom( aFileName ) )
       
   291         {
       
   292         return ETrue;      // files in ROM are always valid
       
   293         }
       
   294 
       
   295     // check if file is WMDRM protected
       
   296     TBool prot =  EFalse;
       
   297     TRAP( err, prot = IsFileWMDRMProtectedL( aFileName ) );
       
   298     if ( err != KErrNone )
       
   299         {
       
   300         ShowErrorNoteL( R_QTN_INFO_FILE_FORMAT_ERROR );
       
   301         return EFalse;
       
   302         }
       
   303     if ( prot )
       
   304         {
       
   305         if ( aIntention == EPlay )
       
   306             {
       
   307             ShowErrorNoteL( R_QTN_INFO_DRM_PREV_RIGHTS_USE );
       
   308             }
       
   309         else
       
   310             {
       
   311             ShowErrorNoteL( R_QTN_INFO_DRM_PROTECTED );
       
   312             }
       
   313         return EFalse;
       
   314         }
       
   315 
       
   316     ContentAccess::TVirtualPathPtr path( aFileName,
       
   317                         ContentAccess::KDefaultContentObject );
       
   318     CData* data = CData::NewLC( path, EContentShareReadWrite );
       
   319     TInt isProtected;
       
   320     err = data->GetAttribute( EIsProtected, isProtected );
       
   321     CleanupStack::PopAndDestroy( data );
       
   322 
       
   323     if ( err != DRMCommon::EOk )
       
   324         {
       
   325         // DRM Helper class knows at least rights db corrupted error message.
       
   326         // Leaves on system-wide error code.
       
   327         iDRMHelper->HandleErrorL( err, aFileName );
       
   328         return EFalse;
       
   329         }
       
   330 
       
   331     // Obtain information whether the file can be set as automated content
       
   332     TBool canSetAutomated = EFalse;
       
   333     TInt canSetAutomatedErr = 
       
   334          iDRMHelper->CanSetAutomated( aFileName, canSetAutomated );
       
   335 
       
   336     if ( !isProtected && canSetAutomated )
       
   337         {
       
   338         // The file in question is not DRM protected.
       
   339         // Return ETrue if file is also Ok unprotected, otherwise EFalse.
       
   340         TBool ret = IsFileValidUnprotectedL( aFileName, aIntention );
       
   341         return ret;
       
   342         }
       
   343     
       
   344     // Operator requirement: Check restrictions if file is mp4 audio
       
   345     if ( iVariation->IsBlockedProtectedType( dataType ) )
       
   346         {
       
   347         ShowErrorNoteL( R_QTN_INFO_DRM_PROTECTED );
       
   348         return EFalse;
       
   349         }
       
   350     
       
   351     if ( canSetAutomatedErr == DRMCommon::ERightsExpired ||
       
   352         canSetAutomatedErr == DRMCommon::ENoRights )
       
   353         {
       
   354         // Rights are expired, future rights or missing
       
   355         iDRMHelper->HandleErrorL( canSetAutomatedErr, aFileName );
       
   356         return EFalse;
       
   357         }
       
   358 
       
   359     // Operator requirement: Check DRM v2 tones
       
   360     if ( !canSetAutomated )
       
   361         {
       
   362         // This is DRM v2 file OR count based v1 tone
       
   363         if ( aIntention == EPlay )
       
   364             {
       
   365             ShowErrorNoteL( R_QTN_INFO_DRM_PREV_RIGHTS_USE );
       
   366             }
       
   367         else
       
   368             {
       
   369             ShowErrorNoteL( R_QTN_INFO_DRM_PROTECTED );
       
   370             }
       
   371         return EFalse;
       
   372         }
       
   373             
       
   374     TInt32 infoBits( 0x00000000 );
       
   375 
       
   376     // Find out rights information
       
   377     if ( !GetFileInfoL( aFileName, infoBits ) )
       
   378         {
       
   379         // Corrupted file or "No rights" situation
       
   380         return EFalse;
       
   381         }
       
   382 
       
   383     // Operator requirement: Check CFM protection
       
   384     if ( infoBits & ENoRingingTone )
       
   385         {
       
   386         // This is CFM protected file, ringingtone is set to "no"
       
   387         if ( aIntention == EPlay )
       
   388             {
       
   389             ShowErrorNoteL( R_QTN_INFO_DRM_PREV_RIGHTS_USE );
       
   390             }
       
   391         else
       
   392             {
       
   393             ShowErrorNoteL( R_QTN_INFO_DRM_PREV_RIGHTS_SET );
       
   394             }
       
   395         return EFalse;
       
   396         }
       
   397       
       
   398     if ( aIntention == ESelect )
       
   399         {
       
   400         // Rights are good to go, and intention is selection
       
   401         // call SetAutomatedPassive to show 'activation query' 
       
   402         iDRMHelper->SetAutomatedType( iAutomatedType );
       
   403         err = iDRMHelper->SetAutomatedPassive( aFileName );
       
   404         if ( err != KErrCancel )
       
   405             {
       
   406             // User accepted dialog
       
   407             User::LeaveIfError( err );
       
   408             // New way, does not require DRM capability
       
   409             data = CData::NewLC( path, EContentShareReadWrite );
       
   410             err = data->ExecuteIntent( ContentAccess::EPlay );
       
   411             // Wrongly requires DRM after all. According to Risto Vilkman
       
   412             // from DRM, KErrAccessDenied can be ignored, since if
       
   413             // CanSetAutomated says the tone is OK, it's OK.
       
   414             if ( err != KErrNone && err != KErrAccessDenied )
       
   415                 {
       
   416                 User::Leave( err );
       
   417                 }
       
   418             CleanupStack::PopAndDestroy( data );
       
   419             }
       
   420         else
       
   421             {
       
   422             // User canceled dialog
       
   423             return EFalse;
       
   424             }
       
   425         }
       
   426         
       
   427     return ETrue;
       
   428     }
       
   429 
       
   430 
       
   431 // -----------------------------------------------------------------------------
       
   432 // CMFProtectionHandler::GetFileInfo
       
   433 //
       
   434 // Function is copy from CFLDDRMImplementation.cpp.
       
   435 // -----------------------------------------------------------------------------
       
   436 //
       
   437 TBool CMFProtectionHandler::GetFileInfoL( const TDesC& aFileName,
       
   438                                           TInt32& aInfoBits )
       
   439     {
       
   440     DRMCommon::TContentProtection contentProtection; // ignored
       
   441     HBufC8* mimeType = NULL; // ignored
       
   442     TUint dataLength = 0; // ignored
       
   443     HBufC8* contentURI = NULL;
       
   444        
       
   445     // Obtain content URI
       
   446     TInt error = iDRMCommon->GetFileInfo(
       
   447         aFileName, contentProtection, mimeType, contentURI, dataLength );
       
   448     delete mimeType;
       
   449 
       
   450     if ( error != DRMCommon::EOk )
       
   451         {
       
   452         delete contentURI;
       
   453         // Handle possible corrupt file situation
       
   454         iDRMHelper->HandleErrorL( error, aFileName );
       
   455         return EFalse;
       
   456         }
       
   457 
       
   458     // Obtain rights object
       
   459     CDRMRights* rights = NULL;
       
   460     error = iDRMCommon->GetActiveRights( *contentURI, DRMCommon::EPlay, rights );
       
   461     delete contentURI;
       
   462 
       
   463     if ( error == DRMCommon::ENoRights )
       
   464         {
       
   465         delete rights;
       
   466         // There is no rights for given file
       
   467         // Should never arrive here, ENoRights is handled
       
   468         // already in IsFileValidL()
       
   469         iDRMHelper->HandleErrorL( error, aFileName );
       
   470         return EFalse;
       
   471         }
       
   472      
       
   473     // Obtain infobits ( needed only for CFM case )     
       
   474    aInfoBits = rights->GetPermission().iInfoBits;
       
   475    delete rights;
       
   476    return ETrue;
       
   477    }
       
   478 
       
   479 
       
   480 // -----------------------------------------------------------------------------
       
   481 // CMFProtectionHandler::IsFileWMDRMProtectedL
       
   482 //
       
   483 // Function is copy from CFLDDRMImplementation.cpp.
       
   484 // -----------------------------------------------------------------------------
       
   485 //
       
   486 TBool CMFProtectionHandler::IsFileWMDRMProtectedL( const TDesC& aFileName )
       
   487     {
       
   488     TBool res = EFalse;
       
   489     RFile hFile;
       
   490 
       
   491     TInt err = hFile.Open( iFsSession, aFileName, 
       
   492                            EFileRead | EFileStream | EFileShareReadersOnly );
       
   493     if ( err == KErrInUse )
       
   494         {
       
   495         err = hFile.Open( iFsSession, aFileName, 
       
   496                         EFileRead | EFileStream | EFileShareAny );
       
   497         }
       
   498     if ( err != KErrNone )
       
   499         {
       
   500         User::Leave( err );
       
   501         }
       
   502     CleanupClosePushL( hFile );
       
   503 
       
   504     TPtrC agent( KNullDesC );
       
   505     DRM::CDrmUtility* drmUtil = DRM::CDrmUtility::NewLC();
       
   506     drmUtil->GetAgentL( hFile, agent );
       
   507     if ( agent.Compare( DRM::KDrmWMAgentName ) == 0 )
       
   508         {
       
   509         res = ETrue;
       
   510         }
       
   511     CleanupStack::PopAndDestroy( drmUtil );
       
   512 
       
   513     CleanupStack::PopAndDestroy( &hFile );
       
   514     return res;
       
   515     }
       
   516 
       
   517 
       
   518 // -----------------------------------------------------------------------------
       
   519 // CMFProtectionHandler::DataTypeL
       
   520 //
       
   521 // Function is copy from CFLDDRMImplementationCommon.cpp.
       
   522 // -----------------------------------------------------------------------------
       
   523 //
       
   524 TDataType CMFProtectionHandler::DataTypeL( const TDesC& aFileName )
       
   525     {
       
   526     TUid dummyUid = { 0 };
       
   527     TDataType dataType( dummyUid );
       
   528     User::LeaveIfError(
       
   529         iApaLsSession.AppForDocument( aFileName, dummyUid, dataType ) );
       
   530 
       
   531     return dataType;
       
   532     }
       
   533 
       
   534 
       
   535 // -----------------------------------------------------------------------------
       
   536 // CMFProtectionHandler::IsFileValidUnprotectedL
       
   537 // 
       
   538 // Function is copy from CFLDDRMImplementationCommon.cpp.
       
   539 // -----------------------------------------------------------------------------
       
   540 //
       
   541 TBool CMFProtectionHandler::IsFileValidUnprotectedL( const TDesC& aFileName,
       
   542                                                      TIntention aIntention )
       
   543     {
       
   544     TDataType dataType = DataTypeL( aFileName );
       
   545     TBuf<KMaxDataTypeLength> mimeType;
       
   546     
       
   547     mimeType = dataType.Des();
       
   548 
       
   549     if ( iVariation->IsBlockedType( mimeType ) )
       
   550         {
       
   551         if ( aIntention == ESelect )
       
   552             {
       
   553             ShowErrorNoteL( R_QTN_INFO_TEXT_NOT_ALLOWED );
       
   554             }
       
   555         return EFalse;
       
   556         }
       
   557 
       
   558     // Operator requirement. Check if DRM is required with tones.
       
   559     if ( aIntention == EPlay )
       
   560         {
       
   561         if ( iVariation->IsBlockedDemoPlayType( mimeType ) )
       
   562             {
       
   563             return EFalse;
       
   564             }
       
   565         }
       
   566     else
       
   567         {
       
   568         if ( iVariation->IsBlockedUnprotectedType( mimeType ) )
       
   569             {
       
   570             ShowErrorNoteL( R_QTN_INFO_NO_DRM );
       
   571             return EFalse;
       
   572             }
       
   573         }
       
   574 
       
   575     return ETrue;
       
   576     }
       
   577 
       
   578 
       
   579 // -----------------------------------------------------------------------------
       
   580 // CMFProtectionHandler::CheckFileSize
       
   581 //
       
   582 // -----------------------------------------------------------------------------
       
   583 //
       
   584 TBool CMFProtectionHandler::CheckFileSize( const TDesC& aFile, const TDesC& aMimeType  )
       
   585     {
       
   586     _LIT( KVideo, "video" );
       
   587     const TInt KKiloByte = 1024;
       
   588     const TInt KSmallSize = 10;
       
   589    
       
   590     if ( iMaxFileSize < KSmallSize )
       
   591         {
       
   592         return ETrue; // too small size limit
       
   593         }
       
   594     if ( aMimeType.Find( KVideo ) != KErrNotFound )
       
   595         {
       
   596         return ETrue;  // only audio files are checked
       
   597         }
       
   598     
       
   599     TEntry entry;
       
   600     TInt err = iFsSession.Entry( aFile, entry );
       
   601     if ( err == KErrNone && iMaxFileSize != KErrNotFound )
       
   602         {
       
   603         TInt size = iMaxFileSize * KKiloByte; // KBytes -> Bytes
       
   604         if ( entry.iSize > size )
       
   605             {
       
   606             return EFalse;
       
   607             }
       
   608         }
       
   609   
       
   610     return ETrue;
       
   611     }
       
   612 
       
   613 
       
   614 // -----------------------------------------------------------------------------
       
   615 // CMFProtectionHandler::ShowErrorNoteL
       
   616 // 
       
   617 // -----------------------------------------------------------------------------
       
   618 //
       
   619 void CMFProtectionHandler::ShowErrorNoteL( TInt aResourceId )
       
   620     {
       
   621     TMFDialogUtil::ShowInformationNoteL( aResourceId );
       
   622     }
       
   623 
       
   624 
       
   625 //  End of File