如何获得Symbian手机中的通讯录
本文分析中有网易博客中摘录,病毒可能也会利用下面功能获取用户隐私.
1.电话簿API也叫Contacts API,是SymbianOS的通信簿API在Series60上的实现.大部分通信簿引擎API与电话簿API是
重复的,只有在电话簿API不能满足需要时才使用通信簿API.
2.Symbian OS手机的通讯录采用文件方式存储。 每个Symbian OS手机都有一个默认的通讯录数据库,在os7.0s中存
在为c:\system\data\Contacts.cdb,在os9.0s中存在为c:\private\100012a5\DBS_100065FF_Contacts.cdb。
3.Symbian OS的手机通讯录在开发上的操作依靠Symbian OS通讯录模型(Contacts Model)实现。
通讯录模型由通讯录数据库、通讯录条目(项)和通信录域三者组成.
他们之间的关系是:一个手机除了系统自带的默认通讯录数据库外,还可以带多个通讯录数据库;一个通讯录数
据库有多个通讯录条目组成,每个条目就是每个联系人,具体数量限制各个手机应该不一样;而一个通讯录条目又
由多个通讯录域组成,如姓名、工作手机号码、家庭手机号码等等,每个项就是一个域。
4.Symbian OS将Contacts Model操作封装为几个类,主要有:
(1).CContactDatabase(通讯录数据库类):除了负责新建、打开、关闭等基本数据库操作外,还负责数据库更
新(通讯录条目的新建、修改、删除需要通过CContactDatabase类的操作才能实现)、排序和查找,另外还有一些
建立快速拨号之类的操作也是通过它来实现。
(2).CContactItem(通讯录条目类):由唯一的一个TContactItemId(typedef TInt32 TContactItemId)标识
,负责具体一个通讯录条目的创建、修改,其直接管理每一个通讯录域
(3).CContactItemField(域类):每一个域就是一个真实单一的数据,该数据的类型由存储类型(TStorageTy
pe)和域类型(TFieldType)同时决定,具体的四种存储类型和多种域类型定义见系统头文件cntdef.h内的定义。
(4).还有很多其它的类,如CContactItemFieldSet(域集类)、CContactFieldStorage(与存储基类)、CCont
actTextFields(文本存储域类)、MContactDbObserver(通讯数据库观察类)等.
(5).使用这些类需要包含一些头文件,如
#include <CNTDB.H> //for CContactDatabase
#include <CNTITEM.H> //for CContactItem
#include <CNTFIELD.H>//for CContactItemField
#include <CNTFLDST.H>//for CContactTextField
#include <CNTFILT.H> //for CCntFilter
在.mmp文件中添加 LIBRARY cntmodel.lib
另外需要添加一些能力: ReadUserData,WriteUserData
5.以下是对通讯录数据库的一些基本的操作:
(1). 打开和关闭数据库
CContactDatabase::OpenL()函数有两个重载函数。如果该函数没有给出一个参数,就打开默认的数据库。另
一种情况是,可以传递一个有关数据库的路径和文件名,规定打开一个指定数据库,请参考sdk文档。
//打开默认数据库
CContactDatabase* contactsDb = CContactDatabase::OpenL();
CleanupStack::PushL(contactsDb);
//取得当前数据库所有通讯条目数
TInt numberOfContacts = contactsDb->CountL();
//释放数据库
CleanupStack::PopAndDestroy(contactsDb);
需要注意:通信录数据库并不具有Close()函数或类似的函数,否则压入清除栈时就得用CleanupClosePushL(
)函数了.
(2). 创建数据库
CContactDatabase::CreateL()函数,如果该数据库已经存在,会以KErrAlreadyExists退出。而CContactDatab
ase::ReplaceL()函数结果总会创建或替换一个数据库,如果没有定义参数,这些函数将创建一个默认的数据库。C
ContactDatabase::FindContactFile()函数接受一个描述符,如果不存在默认数据库的话,该描述符就会返回该默
认数据库的位置。在第三版中,这个函数的作用被ContactDatabaseExistsL()替代,它接受的参数类型是const的.
_LIT(KDbFileName,"c:newdatabase.cdb");
CContactDatabase* newDefaultContactDb;
//是否存在默认数据库
if(CContactDatabase::ContactDatabaseExistsL(KDbFileName)) //FindContactFile
{
newDefaultContactDb = CContactDatabase::ReplaceL(KDbFileName);
}
else
{
newDefaultContactDb = CContactDatabase::CreateL(KDbFileName);
}
CleanupStack::PushL(newDefaultContactDb);
// 添加自己功能代码
CleanupStack::PopAndDestroy(newDefaultContactDb);
结果在第三版模拟器运行时,会在\Epoc32\winscw\c\private\100012a5中创建DBS_100065FF_newdatabase.cdb
(3). 添加通讯录条目
CContactDatabase* contactsDb = CContactDatabase::OpenL();
CleanupStack::PushL(contactsDb);
// 字符串声明
_LIT(KForenameLabel,"Forename");//三个标签
_LIT(KSurnameLabel,"Surname");
_LIT(KWorkPhoneLabel,"Work Phone");
_LIT(KForename,"Steve");//名,姓,电话号码
_LIT(KSurname,"Wilkinson");
_LIT(KWorkPhone,"+441617779700");
// 建立一个新条目
CContactItem* contact = CContactCard::NewLC();//CContactCard继续自CContactItem
//创建一个新的文本存储类型的familyname域
CContactItemField* field
= CContactItemField::NewLC(KStorageTypeText, KUidContactFieldFamilyName);
//将姓域建立与vCard的映射
field->SetMapping(KUidContactFieldVCardMapUnusedN);
//设置域标签
field->SetLabelL(KSurnameLabel);
//设置域值
field->TextStorage()->SetTextL(KSurname);
//把该域加入到新建的条目中
contact->AddFieldL(*field);
CleanupStack::Pop();//*field的控制权交给了contact
//添加文本存储类型的名域
field=CContactItemField::NewLC(KStorageTypeText, KUidContactFieldGivenName);
field->SetMapping(KUidContactFieldVCardMapUnusedN);
field->SetLabelL(KForenameLabel);
field->TextStorage()->SetTextL(KForename);
contact->AddFieldL(*field);
CleanupStack::Pop();
//添加文本存储类型的手机号码域
field=CContactItemField::NewLC(KStorageTypeText, KUidContactFieldPhoneNumber);
field->SetMapping(KUidContactFieldVCardMapTEL);
field->SetLabelL(KWorkPhoneLabel);
field->TextStorage()->SetTextL(KWorkPhone);
contact->AddFieldL(*field);
CleanupStack::Pop();
//把建立的新记录添加到数据库中
contactsDb->AddNewContactL(*contact);
contactsDb->SetOwnCardL(*contact);
CleanupStack::PopAndDestroy(contact);
//释放数据库
CleanupStack::PopAndDestroy(contactsDb);
(4). 读取(遍历)通讯录条目
使用TContactIter类(该类起到数据库操作中类似游标的作用)来遍历一个通信录数据库。它提供了一整套的
函数,用于遍历所有的通信录项。所有的函数都用通信录项ID (TContactItemId) 进行操作,该ID 用于访问某个特
定的通信录项。
CContactDatabase* contactsDb = CContactDatabase::OpenL();
CleanupStack::PushL(contactsDb);
TContactIter iter(*contactsDb);
TContactItemId cardId;
RFileLogger log;
log.Connect();
log.CreateLog(_L("Log"),_L("Log.txt"),EFileLoggingModeAppend);
//The count includes non-system template items
TInt count=contactsDb->CountL();
TInt templateCount=contactsDb->TemplateCount();
log.WriteFormat(_L("count:%d"),count);
log.WriteFormat(_L("templateCount:%d"),templateCount);
//循环遍历
while( ( cardId = iter.NextL() ) != KNullContactId )
{
//读取相应项,这里之所以称其card,就是其实际相当于读一个完整的vCard条目
CContactItem* card = contactsDb->ReadContactL(cardId);
CleanupStack::PushL(card);
log.WriteFormat(_L("TContactItemId:%d"),cardId);
//判断如果不是模板
if (KErrNotFound==contactsDb->GetCardTemplateIdListL()->Find(card->Id()))
{
CContactItemFieldSet& fields=card->CardFields();
TInt fieldCount=fields.Count();
for (TInt i=0;i<fieldCount;i++)
{
CContactItemField& field=fields[i];
//打印标签和值
log.Write(field.Label());
log.Write(field.TextStorage()->Text());
}
contactsDb->CloseContactL(card->Id());
CleanupStack::PopAndDestroy(); // card
}
}
log.CloseLog();
log.Close();
CleanupStack::PopAndDestroy();
(5). 查找并更新通讯录条目
本实例采用查找函数FindAsyncL,该函数声明为:
CIdleFinder * CContactDatabase::FindAsyncL(const TDesC &aText, const CContactItemFieldDef *aField
Def, MIdleFindObserver *aObserver);
为了实现回调,把CContactDatabase* iContactsDb;CIdleFinder * iFinder; 定义为类的成员变量.
void CContactEngine::FindContactL(MIdleFindObserver* aObserver)
{
iContactsDb = CContactDatabase::OpenL();
CContactItemFieldDef* iFieldDef = new (ELeave)CContactItemFieldDef();
CleanupStack::PushL(iFieldDef);
iFieldDef->AppendL(KUidContactFieldGivenName);
iFieldDef->AppendL(KUidContactFieldFamilyName);
_LIT(KFindToken, "Wilkinson");
iFinder = iContactsDb->FindAsyncL( KFindToken, iFieldDef, aObserver);
CleanupStack::PopAndDestroy();// iFieldDef
}
再由观察者来调用如下函数,实现修改
void CContactEngine::EditContactL()
{
if(iFinder->IsComplete())
{
if(iFinder->Error() == KErrNone)
{
CContactIdArray* result = iFinder->TakeContactIds();
CleanupStack:: PushL(result);
for(TInt i=0; i<result->Count(); i++)
{
TContactItemId cardId = (*result)[i];
CContactItem* ownCard = iContactsDb ->OpenContactL(cardId);
CleanupStack::PushL(ownCard);
TInt index = ownCard->CardFields().Find(KUidContactFieldGivenName);
_LIT(KGivenName,"weike");
ownCard->CardFields()[index].TextStorage()->SetTextL(KGivenName);
index = ownCard->CardFields().Find(KUidContactFieldFamilyName);
_LIT(KFamilyName,"wang");
ownCard->CardFields()[index].TextStorage()->SetTextL(KFamilyName);
//提交所做的修改
iContactsDb ->CommitContactL(*ownCard);
//如果这里不做更改可以调用CloseContactL直接关闭
//iContactsDb->CloseContactL(ownCard->Id());
CleanupStack::PopAndDestroy();// ownCard
}
CleanupStack::PopAndDestroy();//result;
}
}
}
(6). 导出所选通讯录条目到文件(vCard)
使用了CContactDatabase类中ExportSelectedContactsL函数,与前次遍历方法不同,并且在取所有通讯条目前
加了一个过滤器CCntFilter类,例程如下:
void CContactEngine::ExportContactL(const TDesC& aFileName)
{
RFs fileSession;
//连接文件服务器
User::LeaveIfError(fileSession.Connect());
CleanupClosePushL(fileSession); //1
//打开默认数据库
CContactDatabase* contactDb = CContactDatabase::OpenL();
CleanupStack::PushL(contactDb); //2
//新建过滤器
CCntFilter* filter = CCntFilter::NewLC(); //3
filter->SetContactFilterTypeALL(EFalse);
//按vCard格式导出
filter->SetContactFilterTypeCard(ETrue);
//安装filter
contactDb->FilterDatabaseL(*filter);
//取出满足条件的记录数据项数组
CContactIdArray* exportContact = CContactIdArray::NewL(filter->iIds);
CleanupStack::PushL(exportContact); //4
RFile file;
//新建文件,aFileName是文件名字
file.Replace(fileSession,aFileName,EFileWrite);
CleanupClosePushL(file); //5
//声明文件流
RFileWriteStream outputStream(file);
CleanupClosePushL(outputStream); //6
TUid id;
id.iUid = KVersitEntityUidVCard;
//导出到文件
contactDb->ExportSelectedContactsL(id,*exportContact, outputStream, CContactDatabase::EExcludeUid);
CleanupStack::PopAndDestroy(6);
}
使用时如: iContactEngine->ExportContactL(_L("c:\\mycards.txt"));
6.S60特有的通讯录操作API引擎
nokia专门为S60平台SDK建立了一个操作通讯录的引擎,以及一些封装的类,主要有:
(1).CPbkContactEngine(通讯录引擎类):如果已经存在一个缺省数据库,CPbkContactEngine::NewL()
就连接到该数据库,否则创建该数据库。也可以传入文件名,打开一个指定的通讯录数据库.
从它的头文件cpbkcontactengine.h可以看出,它是对CContactDatabase和观察器类MContactDbObserver封装了
下并进行了一些优化,简便了我们操作时的一些代码,为此操作起来比较方便。
(2).CPbkContactItem(通讯录条目类):该类头文件是CPbkContactItem.h,主要对通讯录条目类CContactItem的
封装和优化,可以看出很多导出函数都是一致的。
(3).TPbkContactItemField(域类):该类的头文件tpbkcontactitemfield.h。
使用这些类需要的能力有: ReadUserData WriteUserData ReadDeviceData WriteDeviceData
导入库 PbkEng.lib
(4) 新建通讯录条目
_LIT(KFName,"andy");
_LIT(KLName,"lau");
_LIT(KNumber,"13794417723");
//运用引擎打开默认通讯录
CPbkContactEngine* iPbkContactEngine = CPbkContactEngine::NewL();
CleanupStack::PushL(iPbkContactEngine);//1
//新建一空通信录项
CPbkContactItem* contact = iPbkContactEngine->CreateEmptyContactL();
CleanupStack::PushL(contact); //2
//设置first name 域
TPbkContactItemField* field = contact->FindField(EPbkFieldIdFirstName);
field->TextStorage()->SetTextL(KFName);
//设置last name 域
field = contact->FindField(EPbkFieldIdLastName);
field->TextStorage()->SetTextL(KLName);
//设置手机号码域
field = contact->FindField(EPbkFieldIdPhoneNumberMobile);
field->TextStorage()->SetTextL(KNumber);
//可以添加其他值域
//...
//修改后结果添加到数据库中,并返回这个通信录项的id,该id可以以后使用
TContactItemId Id = iPbkContactEngine->AddNewContactL(*contact);
CleanupStack::PopAndDestroy(2);
(5). 修改通讯录条目
_LIT(number,"13794417723");
TBuf<11> phonenumber(number);
CPbkContactEngine* iPbkContactEngine = CPbkContactEngine::NewL();
CleanupStack::PushL(iPbkContactEngine);//1
//需要提供一个通讯录条目id
TContactItemId id(23);
//这里打开条目后加锁,以防其它客户端打开
CPbkContactItem* contact = iPbkContactEngine->OpenContactLCX(id); //aContactId
//找到需要修改的field
TPbkContactItemField* field = contact->FindField(EPbkFieldIdPhoneNumberMobile);
//设置并确认修改
field->TextStorage()->SetTextL(phonenumber);
iPbkContactEngine ->CommitContactL(*contact);
CleanupStack::PopAndDestroy(3);