Symbian端彩信读取初探
上周由于项目需要对彩信读取进行了预研,虽然并未涉及彩信的拦截,只是对Symbian S60手机收件箱中的彩信和彩信通知的内容进行提取并分析,但是也算是趟了下之前一直没有搞过的彩信这浑水,小结一下。为了体现分析过程的逻辑性,下文并非是由简入繁,而是采用由已知到未知的说明过程来进行。
彩信和彩信通知的区别
正如前面博文《Symbian OS中的消息存储与常用操作》中提及的那样,手机中的各种消息都是以数据项(Entry)形式供程序操作,鉴于Nokia之前的SDK有众多API采用非公开的方式,很多东西碰上了只能拼命的试,试通了就算OK了,短信就是这个样子的,自从Cxt将发送短信的代码调通后,目前用的代码基本都是他那份原型。写了这么多只想说虽然手机中的各种消息都是以数据项形式给出的,但是数据项又由文件夹类型、消息类型、附件类型和服务类型的数据项,我们所指的消息如短信、彩信和邮箱乃至用户自定义的一些消息等等都是消息类型的数据项即KuidMsvMessageEntry类型。既然消息类型的数据项这么多,那么如何区分呢,这就靠各自不同的Mtm类型,由于SDK没有系统性阐述,我们虽然已经知道短信的类型是KUidMsgTypeSMS
,但是彩信的Mtm类型值到底是什么,它跟彩信通知是否是有区分的,我们不得而知。
没办法只能通过数据项的通用操作,对手机中已存的彩信和彩信通知进行读取后进行分析内容,才得知彩信和彩信通知是两个完全不同的Mtm类型值,一个是0x100058E1,另一个是0x 100059C 8,后来通过google搜索才知道前者的宏定义类型为KuidMsgTypeMultimedia,后者的宏定义类型为KUidMsgMMSNotification,两者在mmsconst.h头文件中有定义。
同样使用通用的数据项操作,我们可以获取彩信和彩信通知消息数据项的消息存储
CMsvStore和附件情况,两者的CMsvStore都是有数据的(SizeL()函数返回都是大于0的),但是却没有文本体(HasBodyTextL()返回都是Efalse);另外彩信都是有附件的,而彩信通知一般是没有附件的。
因为附件本身也是数据项(KuidMsvAttachmentEntry类型的数据项),所以我们可以更进一步通过通用的数据项操作将彩信的所有附件全部都读取出来。而附件类型由文件类型、链接类型和数据项类型三种,通常彩信中的附件都是文件类型的附件,我们用以下代码来演示将附件拷贝出来
CMsvStore* inboxStore= iSmsMtm->Entry().ReadStoreL();
CleanupStack::PushL(inboxStore);
MMsvAttachmentManager& attachManager = inboxStore->AttachmentManagerL();
TInt attachmentCount = attachManager.AttachmentCount();
for(TInt i=0;i< attachmentCount;i++)
{
CMsvAttachment *attachment = attachManager.GetAttachmentInfoL(i);
CleanupStack::PushL(attachment);
if(type != CMsvAttachment::EMsvMessageEntry)
{
TFileName newPath(_L("c:\\data\\"));
newPath.AppendNum(aMessageId);
newPath.Append(_L("LLF"));
newPath.Append(attachment->AttachmentName());
RFile file = attachManager.GetAttachmentFileL(i);
TInt size(0);
file.Size(size);
HBufC8 *buf = HBufC8::NewLC(size);
TPtr8 ptrBuf(buf->Des());
file.Read(ptrBuf, size);
RFile newFile;
User::LeaveIfError(newFile.Replace(iFs, newPath, EFileWrite));
newFile.Write(ptrBuf);
newFile.Close();
CleanupStack::PopAndDestroy(buf);
}
CleanupStack::PopAndDestroy();
}
CleanupStack::PopAndDestroy();
为了更加直观的区分各个附件隶属于不同的消息,为此以消息ID打头并加LLF来区分附件的各个文件,一条家庭生活报的彩信,我获取到13个附件的截图如下
其中有smil文件,有图片文件,也有文本文件。
使用通用的数据项操作方法,借助SDK我们对彩信和彩信通知的内容区别和内容分析只能到这里了。
彩信的内容分析
显然如果仅仅知道以上内容还是不够的,困扰我们的
CMsvStore里面到底有些什么东西呢?暂时从代码里面走出去,我们来参阅下Nokia发布的一份难得的中文文档《CH_How_To_Create_MMS_Services_v4_0.pdf》(详见附件),正如短信有短信的PDU一样,彩信也是有其PDU的,这个PDU就在这个文档的第五部分,在这里就不详述了,从里面截个图过来说明下问题。
由这个MMS的PDU,我们可以猜想下附件就是Message Body,而
CMsvStore是否就是MMS Header的内容呢?
幸好现在Nokia将Symbian开源了,否则这个猜想是没有办法揭开了,通过Symbian网站上的源代码浏览器,我们可以搜到彩信相关的源代码位于sf\app\messaging文件夹下,但是这个里面除了彩信之外,短信也有,邮箱也有,到底该如何找到需要的彩信呢,我们知道短信其实也有短信头的,在SDK中有提供CSmsHeader
,怀疑彩信也应该有这么头才是,于是用C*Header搜索文件夹,过来能够搜索到一个mmsheaders.h头文件中有一个CMmsHeaders,找到了这个离胜利就不远了,联想到短信读取时有一个CSmsClientMtm::LoadMessageL()函数,索性就找找有没有一个CMmsClientMtm::LoadMessageL()函数的,通过sf\app\messaging\mmsengine\clientmtm\src文件加下的mmsclient.cpp果然能找到这个函数,其源代码如下
// ---------------------------------------------------------
// CMmsClientMtm::LoadMessageL
// Loads the multimedia message
// ---------------------------------------------------------
//
void CMmsClientMtm::LoadMessageL()
{
// First we should assert that iMsvEntry is not NULL, and panic, if it is
__ASSERT_DEBUG( iMsvEntry, gPanic( EMmsNoCMsvEntrySet ) );
// LoadMessageL should only be supported for message entries.
if ( iMsvEntry->Entry().iType.iUid != KUidMsvMessageEntryValue )
{
iAttributes->Reset();
iMmsHeaders->Reset();
iAddresseeList->Reset();
return;
}
// Old data must be reset first....
iAttributes->Reset();
// load the correct data
// get read-only message store
CMsvStore* store = iMsvEntry->ReadStoreL();
CleanupStack::PushL( store );
// restore headers of multimedia message
// Attachment info is not restored.
// It makes no sense to cache the attachment info as new attachments
// can be added with the help of the attachment magager without
// informing MMS Client MTM of the additions.
// Caller must use attachment manager to get attachment info.
iMmsHeaders->RestoreL( *store );
RestoreAttributesL( *store );
CleanupStack::PopAndDestroy( store );
store = NULL;
// Build the iAddresseeList up
BuildAddresseeListL();
}
注意源码中红色字体部分,我是英盲,所以也不管这个restore是啥意思,直接看CMmsHeaders里面的头文件定义和源码
头文件定义
/**
* Internalize the headers.
* @param aStore CMsvStore
*/
IMPORT_C void RestoreL( CMsvStore& aStore );
其实看了头文件里面的定义注释,初始化头,参数是
CMsvStore我们就知道是怎么回事了,不过既然有源码,那就还是贴下源码,至于分析我就不做了,反正跟短信读取CSmsHeader一样,操作这个CMmsHeaders。
// ---------------------------------------------------------
// CMmsHeaders::RestoreL
//
// ---------------------------------------------------------
//
EXPORT_C void CMmsHeaders::RestoreL(
CMsvStore& aStore )
{
RMsvReadStream stream;
Reset( NULL ); // all old pointers are deleted here
if ( aStore.IsPresentL( KUidMmsHeaderStream ) )
{
stream.OpenLC( aStore, KUidMmsHeaderStream ); // pushes 'stream' to the stack
InternalizeL( stream );
CleanupStack::PopAndDestroy( &stream ); // close stream
}
// restore MMBox header streams if present
if ( aStore.IsPresentL( KUidMMsElementDescriptorStream ) )
{
stream.OpenLC( aStore, KUidMMsElementDescriptorStream ); // pushes 'stream' to the stack
iElementDescriptor = new( ELeave )CMmsElementDescriptor;
iElementDescriptor->InternalizeL( stream );
CleanupStack::PopAndDestroy( &stream ); // close stream
}
if ( aStore.IsPresentL( KUidMMsMMBoxMessageHeaderStream ) )
{
stream.OpenLC( aStore, KUidMMsMMBoxMessageHeaderStream ); // pushes 'stream' to the stack
iMmBoxMessageHeaders = CMmsMMBoxMessageHeaders::NewL();
iMmBoxMessageHeaders->InternalizeL( stream );
CleanupStack::PopAndDestroy( &stream ); // close stream
}
if ( aStore.IsPresentL( KUidMMsMMBoxViewHeadersStream ) )
{
stream.OpenLC( aStore, KUidMMsMMBoxViewHeadersStream ); // pushes 'stream' to the stack
iMmBoxViewHeaders = new( ELeave )CMmsMMBoxViewHeaders;
iMmBoxViewHeaders->InternalizeL( stream );
CleanupStack::PopAndDestroy( &stream ); // close stream
}
// Extended notification also restored here
TInt length = 0; // string length
// Completeness indicator is not saved or restored.
// If we have stored the text, it normally indicates that the message is not complete
if ( aStore.IsPresentL( KUidMMsExtendedNotificationStream ) )
{
stream.OpenLC( aStore, KUidMMsExtendedNotificationStream ); // pushes 'stream' to the stack
length = stream.ReadInt 32L ();
if ( length > 0 )
{
iExtendedNotificationText = HBufC::NewL( stream, length );
}
CleanupStack::PopAndDestroy( &stream ); // close stream
}
if ( aStore.IsPresentL( KUidMmsApplicationInfoStream ) )
{
stream.OpenLC( aStore, KUidMmsApplicationInfoStream ); // pushes 'stream' to the stack
length = stream.ReadInt 32L ();
if ( length > 0 )
{
iApplicationId = HBufC::NewL( stream, length );
}
length = stream.ReadInt 32L ();
if ( length > 0 )
{
iReplyToApplicationId = HBufC::NewL( stream, length );
}
length = stream.ReadInt 32L ();
if ( length > 0 )
{
iApplicationInfo = HBufC8::NewL( stream, length );
}
CleanupStack::PopAndDestroy( &stream ); // close stream
}
if ( aStore.IsPresentL( KUidMmsReserved ) )
{
stream.OpenLC( aStore, KUidMmsReserved ); // pushes 'stream' to the stack
iRecommendedRetrievalMode = stream.ReadInt 32L ();
length = stream.ReadInt 32L ();
if ( length > 0 )
{
iRecommendedRetrievalModeText = HBufC16::NewL( stream, length );
}
length = stream.ReadInt 32L ();
if ( length > 0 )
{
iReplaceCancelId = HBufC8::NewL( stream, length );
}
iCancelStatus = stream.ReadInt 32L ();
CleanupStack::PopAndDestroy( &stream ); // close stream
}
}
一来由于时间仓促,二来开源的代码头文件和相关库等要拷入工程中去,所以具体的编码调试验证就留给以后正式要用的时候再进行了。
彩信通知的内容分析
以上对彩信的内容分析算是告一个段落了,我们再回过头来看看这个彩信通知,这个消息类型的数据项其mtm为KUidMsgMMSNotification
,那么这个客户端的MTm到底是什么,继续搜索源码,发现在sf\app\messaging\mmsengine\clientmtm\src有一个mmsnotificationclient.cpp其内部正好是CmmsNotificationClientMtm。本来以为其会跟彩信一样比较容易看出结果来,谁知道这个类的源码看了下,感觉有点残缺的味道,里面也有用到彩信的头CMmsHeaders,因为CMmsHeaders就没有去深入,这下也只能打住了。而且还有一个疑问,如何通过这个彩信通知来编程实现手动去获取彩信,又是一个比较繁琐的问题,搜了下源码貌似跟sf\app\messaging\mobilemessaging\mmsui\notmtmsrc文件下的源码有关。看来这趟浑水不太清啊,就此打住了。
暂时将Symbian端的彩信读取分析到这里。以后有机会再继续趟这浑水。
按照常规,由于贴图不会搞,如果需要详尽文档,请下载以下word文档和诺基亚官方文档rar的链接
彩信初探.rar
注意:
目前在6730机子上对草稿箱彩信进行试验时发现
编辑的彩信假如没有经过发送,那么通过之前的attachment->AttachmentName()无法获得附件的文件名;但是在N81等机子上是能够获取到的。虽然通过attachment->AttachmentName()无法获取,但是目前发现通过attachment->FilePath()却可以得到完整的文件路径,为此附件文件名的获取,假设attachment->AttachmentName()获取不到,那么将采用attachment->FilePath()获取文件路径后进行文件名提取的方法来实现比较可靠一些。