Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #106, ОКТЯБРЬ 2007 г.

Леди в черном

Дмитрий «Dem@n» Тарасов

Хакер, номер #106, стр. 116

(dmitry_tarasov@hotmail.com)

Создание blacklist-приложения для смартфонов под Symbian

Довелось мне как-то подвергнуться жестокой DDoS-атаке посредством звонков и sms’ок от одной назойливой барышни. В тот вечер, сидя в одном московском кабаке с другом, мы обратили внимание на тот факт, что производители смартфонов пичкают свои девайсы чем угодно, но только не реально полезным софтом. В частности, что мешало Nokia устанавливать на свои смартфоны бизнес-серии программу, фильтрующую вызовы с определенных номеров? Предлагаю восполнить этот пробел финского гиганта прямо сейчас!

Функционал

Думаю, с инструментарием разработки под Symbian ты уже знаком по нашим предыдущим статьям, посвященным этой теме (если нет, то отложи этот номер и ознакомься с сентябрьским и декабрьским «Кодингом» за прошлый год). В своих экспериментах мы традиционно ориентируемся на смартфоны Series60. Если ты хочешь писать под новое поколение смартов на Symbian 9.X, то рекомендую поставить соответствующий SDK. Функционал у нас будет следующий: приложение должно блокировать входящие sms/mms, а также звонки с определенных номеров.

Начинаем кодить

Приступим к реализации. Очевидно, что наше приложение должно иметь модульную структуру, иначе код превратится в кровавое месиво, что часто случается с плохо спроектированными проектами под Symbian. Не забывай, что C++ сам по себе благоприятствует созданию неудобоваримого для понимания кода, а в совокупности с извращенностью Symbian-парадигм в умелых руках это дает поразительной кривизны результат :). Поэтому предлагаю разбить функционал на модули (смотри рисунок «Структура приложения»).

Посмотрим, что должен делать каждый из модулей по отдельности:

Messaging module:

  • отлов событий «Входящее sms/mms»;
  • определение номера, с которого пришло входящее SMS/MMS;
  • удаление sms/mms при условии, что номер занесен в базу данных black list.

Telephony module:

  • отлов события «входящий вызов»;
  • определение номера звонящего;
  • сброс вызова при условии, что номер занесен в базу данных black list.

Address Book module:

  • интерфейс выбора контакта из адресной книги;
  • запоминание в базу данных black list номера выбранного контакта.

BlackList Engine module

  • интерфейс ввода номера для добавления в базу данных black list;
  • ведение базы данных black list и предоставление интерфейса к ней.

Обрати внимание на то, что модули Messaging module и Telephony module по большому счету являются универсальными и могут быть задействованы практически в любом приложении, использующем возможности телефонии смартфона.

Выбор способа хранения телефонов неудачников я предоставлю тебе - можно использовать встроенные классы для хранения данных, XML или вообще держать их в текстовом файле. Для ясности изложения будем рассматривать последний вариант, а заодно и разберем работу со строковыми дескрипторами.

Посмотрим на реализацию каждого модуля в отдельности.

Messaging engine

Этот модуль будет состоять из одного класса, являющегося так называемым классом Active Object. Active Objects введены в Symbian для поддержки асинхронного выполнения. В частности, подобные объекты используются в тех случаях, когда их работа связана с выполнением продолжительных операций, во время которых работа приложения в целом не должна останавливаться. Кроме того, класс CMessagingEngine будет унаследован от MMsvSessionObserver. Вообще в Symbian довольно много observer-классов, использующихся для отлова определенных событий (как в нашем случае). Обычно используется конструкция вида:

void HandleSomething(…){

switch(aEvent)

{

… //ловим события

}

}

В этом примере мы отлавливаем создание новой sms’ки в папке «Входящие», определяем номер, с которого она пришла, и удаляем ее, если этот номер присутствует в базе лузеров.

Полный код класса CMessagingEngine ты можешь найти на диске. Создавать экземпляр класса нужно в конструкторе класса AppUi нашего приложения следующим образом:

iMessagingEngine = CMessagingEngine::NewL();

Здесь мы вызываем стандартный симбиановский конструктор, о котором тебе стоит почитать в SDK (раздел Leaving Functions).

После создания объекта наше приложение в состоянии отлавливать события входящих sms и mms. Остается решить вопрос их обработки. Для этого в методе HandleSessionEventL(…) класса мы отловим событие EMsvEntriesCreated, которое возникает при создании нового сообщения (причем неважно где: в папке «Входящие», «Черновики» и т.д.). HandleSessionEvent имеет 4 параметра: первый служит для обозначения события и имеет тип TMsvSessionEvent, а три других бестиповые (TAny*) и служат для передачи служебной информации. В частности, для события EMsvEntriesCreated один из этих параметров содержит ID директории, где была создана новая sms/mms, а другой является указателем на множество вновь созданных сообщений. Код отлова сообщения выглядит следующим образом:

Код отлова и удаления непотребных sms

void CMtmsExampleEngine::HandleSessionEventL(TMsvSessionEvent aEvent, TAny* aArg1, TAny* aArg2, TAny* aArg3)

{

switch (aEvent)

{

case EMsvEntriesCreated:

//проверяем, что сообщение создано в папке «Входящие»

if(*static_cast<TMsvId*>(aArg2)==KMsvGlobalInBoxIndexEntryId)

{

//инициализируем массив вновь созданных sms

CMsvEntrySelection* entries = static_cast<CMsvEntrySelection*>(aArg1);

//получаем ID первой созданной sms из массива (проще говоря, айди новой sms)

TMsvId iNewMessageId = entries->At(0);

TMsvId owningServiceId;

//объект sms’ки

TMsvEntry messageEntry;

iSession->GetEntry((*entries)[entries->Count()-1],owningServiceId,messageEntry);

TMsvId iSmsId = messageEntry.Id();

iSmsMtm->SwitchCurrentEntryL(iSmsId);

//строковый дескриптор, куда записывается номер отправителя сообщения

TBuf<20> senderNumber;

senderNumber.Append(iSmsMtm->Entry().Entry().iDetails );

//если номер есть в базе, удаляем

if(iBLEngine.CheckNumber(senderNumber)

DeleteMessageL(iSmsId);

}

}

}

В приведенном коде задействован метод CheckNumber(TDesC& aNumber) класса CBLEngine, который мы рассмотрим позже. С остальным кодом ты можешь ознакомиться в проекте на диске.

Telephony engine

Принцип работы этого модуля в точности повторяет принцип работы предыдущего, разница лишь в классах и в том, что для создания и инициализации движка требуется несколько больше телодвижений. Код его я бы посоветовал попробовать сочинить самому, а если не получится – мой исходник всегда к твоим услугам :).

Реализация движка blacklist

Поскольку с вопросами отлова события и их блокирования мы определились, приступим к созданию основного функционала, который прост как 3 копейки: нам нужно научиться формировать список отстойных номеров и делать к нему интерфейс, позволяющий проверять, есть ли конкретный номер в этом списке. Поскольку мы условились держать номера в обычном текстовом файле, то суть описанного выше сводится добавлению строк в этот файл и их поиску. Для работы с файловой системой в Symbian имеется класс RFile. Тем не менее процедура считывания данных из файла или записи в файл занимает, на мой взгляд, слишком много места, поэтому для дальнейших выкладок мы будем использовать файловый движок, который ты сможешь найти на DVD и который можно использовать в любых Series60-проектах. Рассмотрим функцию добавления данных в файл:

void CFileEngine::AddData(TDesC &aFileName, TDesC &aData, TBool aNewLine)

{

//строковый heap-дескриптор

HBufC* Data=HBufC::NewLC(this->GetFileSize(aFileName)+aData.Length()+3);

if(this->FindFile(aFileName))

{

this->ReadData(aFileName,Data->Des());

if(aNewLine)

{

Data->Des().operator +=(_L("\n"));

}

}

else

{

Data->Des().Copy(_L(""));

}

Data->Des().operator +=(aData);

this->WriteData(aFileName,Data->Des());

}

Как видишь, параметрами функции являются имя файла, в который добавляем данные, строковый дескриптор, содержащий строку данных, и флаг, показывающий, фигачить ли данные в конец файла или добавлять перед этим символ переноса строки. Особый интерес представляет в этом коде дескриптор Data, являющийся структурой, содержащей размер данных и ссылку на них. В начале выполнения функции мы выделяем под данные суммарный размер уже хранящихся данных в файле БД и данных к добавлению. После этого мы считываем в него данные из файла базы, добавляем к ним записываемый номер и загоняем все это дело обратно в файл БД. Механизм, конечно, кривенький, зато простой и эффективный. При этом в методе WriteData того же файлового движка выполняются манипуляции по поиску файла, его открытию, назначения соответствующих флагов и т.д.

Для формирования собственно БД можно использовать 2 метода: задание номера вручную и с помощью доступа к адресной книге смартфона. Второй способ предпочтительнее, поскольку он более user-friendly. Что приятно, в Symbian есть встроенный механизм доступа к движку адресной книги. В частности, с помощью класса CPbkContactEngine ты можешь получить доступ к записям, редактировать их и добавлять новые. В нашем случае нас интересует показ диалога со списком контактов для предоставления пользователю возможности выбора необходимого контакта. После выбора нужно занести номер контакта в черный список. Соответствующий код несколько нетривиален и с первого взгляда сложен для понимания. Полностью и ясно откомментированный сорец, конечно же, я тебе подготовил :).

После создания базы данных контактов наше приложение будет работать в фоновом режиме и ожидать поступления входящих вызовов и сообщений. При обнаружении таковых оно получит либо номер отправителя, либо номер звонящего. После этого вызывается упомянутый выше метод CheckNumber(TDesC& aNumber) класса CBLEngine.

В нем мы делаем следующий финт ушами: записываем содержимое файла БД в один строковый дескриптор, после чего ищем в нем подстроку aNumber. Если строка найдена, значит номер занесен в черный список. В этом случае мы его блокируем. Поиск подстроки выполняется следующим образом:

TBuf<50> iF(_L("C:\\lusers_bd"));

HBufC* fileData=HBufC::NewL(iFileEngine->GetFileSize(iF));

iFileEngine->ReadData(iF,fileData->Des());

if(fileData->Des().Find(aNumber)!=KErrNotFound)

return ETrue;

Здесь мы инициализировали строковый дескриптор iF именем файла базы данных, после чего создали строковый heap-дескриптор, в который записали содержимое этого файла. Метод Find дескрипторов используется для поиска подстроки и возвращает позицию, в которой подстрока встречается в базе, либо KErrNotFound (что есть ноль), если подстроки нет. Вот и все, программа готова!

Заключение

После прочтения этой статьи ты наверняка вставишь DVD в дисковод, откроешь сорцы, заваришь чайку и погрузишься в состояние глубокого внутреннего спокойствия. Еще бы! Ведь теперь ты сможешь мирно спать каждую ночь, и твой чуткий сон больше не потревожат звонки оголтелых фанаток! Ибо все лишние леди у нас теперь в черном! Сегодня мы еще раз убедились, что смартфон – это настоящий компьютер и писать для него одни лишь тетрисы и змейки – редкое расточительство. Мы – за настоящий, юзабельный софт! Вроде этой программки.

Массу полезной информации можно найти на forum.nokia.com.

Содержание